【问题标题】:memcmp but need to compare block with fixed valuememcmp 但需要将块与固定值进行比较
【发布时间】:2013-06-10 22:37:33
【问题描述】:

我需要将一块内存与 C 中的固定值进行比较。我可以用 memcmp 来做这件事吗?比如:

memcmp (starting_address , fixed_value , num_byte)

我需要将 fixed_value 设为固定值,而不是块的起始地址。

  1. 无法将固定值写入整个临时内存块,因为我的空间有限。
  2. 使用循环逐个写入和检查内存不是一种选择,因为它非常慢。

如果不可能,谁能告诉我一个与 memcmp 一样快(或更快)的解决方案?

谢谢,

编辑:假设我有 5GB 的内存可以容纳 0。我正在努力确保它们都是 0。检查块的第一个字节是否安全然后这样做:

memcmp (starting_address , starting_address + ONE_BYTE , FIVE_GB); ?

编辑:这就是为什么我需要使用 memcmp 而不是用户定义的循环:

这段代码运行了 546 个时钟周期:

memset(0x80000000 , 0x1 , 0x10000000);
memset(0x90000000 , 0x1 , 0x10000000);
memcmp(0x80000000 , 0x90000000 , 0x10000000);

vs 这个耗时 7669 个时钟滴答声:

unsigned int i;
int flag = 0;
int *p = 0x80000000;
int *q = 0x90000000;
while(p < 0x90000000)
{
    if(*p++ != *q++)
    {
        flag = 1;
    }
}

【问题讨论】:

  • “使用循环逐个写入和检查内存不是一种选择,因为它非常慢。”你觉得memcmp 会做什么?
  • 在得出memcmp 更快的结论之前,您是否尝试过计时以查看memcmp 与您自己编写的for 循环相比需要多长时间?您是否尝试过在 for 循环中一次读取和比较 32 位或 64 位的块?
  • @CarlNorum:根据我的经验,For 循环的性能甚至不及 memcmp/memcpy。现代处理器具有用于在内存中处理数据的有效指令(想到 REP MOVSB),并且存在额外的循环开销。在 asm 中仍有更快的方法,因为 memcmp/memcpy 旨在处理一般情况,例如涉及的内存不是 DWORD 对齐的情况。
  • 但是那些不一样!您的第二个示例甚至不是有效代码。
  • 两句名言:“你不能得到比'错误'更少的评价”和“如果它不需要产生正确的结果,我可以让它运行得任意快”:-)

标签: c memcmp


【解决方案1】:

我刚刚在我的 Mac 上测试了这个循环,它击败了memcmp

uint64_t *p = (uint64_t *)buffer1;
uint64_t compare;
memset(&compare, 1, sizeof compare);
for (i = 0; i < length/sizeof compare; i++)
{
    if (p[i] != compare)
        break;
}

完整示例代码:

#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <time.h>
#include <stdlib.h>
#include <stdint.h>

// from: http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html
void timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y)
{
    /* Perform the carry for the later subtraction by updating y. */
    if (x->tv_usec < y->tv_usec)
    {
        int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
        y->tv_usec -= 1000000 * nsec;
        y->tv_sec += nsec;
    }

    if (x->tv_usec - y->tv_usec > 1000000)
    {
        int nsec = (x->tv_usec - y->tv_usec) / 1000000;
        y->tv_usec += 1000000 * nsec;
        y->tv_sec -= nsec;
    }

    /* Compute the time remaining to wait. tv_usec is certainly positive. */
    result->tv_sec = x->tv_sec - y->tv_sec;
    result->tv_usec = x->tv_usec - y->tv_usec;
}

int main(int argc, char **argv)
{
    struct rusage before;
    struct rusage after;
    struct timeval diff;
    size_t i;

    size_t length = strtoull(argv[1], NULL, 0);

    char *buffer1 = malloc(length);
    char *buffer2 = malloc(length);

    printf("filling...");
    fflush(stdout);
    memset(buffer1, 1, length);
    memset(buffer2, 1, length);
    printf(" done\n");

    getrusage(RUSAGE_SELF, &before);
    uint64_t *p = (uint64_t *)buffer1;
    uint64_t compare;
    memset(&compare, 1, sizeof compare);
    for (i = 0; i < length/sizeof compare; i++)
    {
        if (p[i] != compare)
            break;
    }
    if (i == length/sizeof compare)
        i = 0;
    getrusage(RUSAGE_SELF, &after);

    printf("\nloop (returned %zu):\n", i);
    timeval_subtract(&diff, &after.ru_utime, &before.ru_utime);
    printf("User:   %ld.%06d s\n", diff.tv_sec, diff.tv_usec);

    timeval_subtract(&diff, &after.ru_stime, &before.ru_stime);
    printf("System: %ld.%06d s\n", diff.tv_sec, diff.tv_usec);

    getrusage(RUSAGE_SELF, &before);
    i = memcmp(buffer1, buffer2, length);
    getrusage(RUSAGE_SELF, &after);

    printf("\nmemcmp (returned %zu):\n", i);
    timeval_subtract(&diff, &after.ru_utime, &before.ru_utime);
    printf("User:   %ld.%06d s\n", diff.tv_sec, diff.tv_usec);

    timeval_subtract(&diff, &after.ru_stime, &before.ru_stime);
    printf("System: %ld.%06d s\n", diff.tv_sec, diff.tv_usec);

    return 0;
}

并运行结果:

$ make
clang -Wall -Wextra -Werror -O3 -g -o example example.c
./example 0x10000000
filling... done

loop (returned 0):
User:   0.024078 s
System: 0.000011 s

memcmp (returned 0):
User:   0.036752 s
System: 0.000017 s

也许你可以做类似的事情?

注意:对于那些担心缓存变暖的人,我也尝试了循环前的 memcmp 并得到了相同的结果。

【讨论】:

  • 谢谢!我错误地认为 memcmp 优于用户定义的循环,至少对于我想要完成的事情。
  • @Arash 不,你没有错。这是一个简单的例子。但是对于一般的缓冲区,具有不同的对齐方式、大小等,memcmp 将优于一个简单的实现。 memcmp 实现考虑了对齐、缓存行大小和其他优化。我建议你再考虑一下。
【解决方案2】:

一种解决方案:

创建一个包含所有相同值的缓冲区并迭代地比较它。这样,您无需编写太多代码即可获得高效的memcmp 实现的优势:

static char val[4096]; // tune the size of the buffer if desired
/* at initialization: memset(val, 0x01, sizeof(val)) */

char *start, *ptr, *end;
// initialize start and end
for(ptr = start; ptr < end-sizeof(val); ptr += sizeof(val)) {
    if(memcmp(ptr, val, sizeof(val))
        goto not_all_val;
}
if(memcmp(ptr, val, end - ptr))
    goto not_all_val;

/* all val */
puts("all val");
return;

not_all_val:
puts("not all val");
return;

性能对比:

memcmp(buf, buf2, 4000000)的10000次迭代(两个相同长度的缓冲区,与问题中的第一种方法相同):

real    0m7.480s
user    0m7.375s
sys 0m0.103s

4000000字节的逐字符比较10000次迭代(与第二种方法相同):

real    0m27.004s
user    0m26.908s
sys 0m0.094s

上述方法的 10000 次迭代超过 4000000 字节:

real    0m3.194s
user    0m3.151s
sys 0m0.042s

YMMV(我用的是三年前的 Macbook Pro),但这种方法比比较完整缓冲区快两倍(可能是由于卓越的缓存性能),并且几乎 比逐字符比较快十倍。

【讨论】:

    【解决方案3】:

    memcmp 带有地址比较内存块的最佳选择。无论您使用块内联还是使用块的内存地址,您仍然必须将块存储在某个地方。

    您可以在编译时创建这样的块,例如:

    int block[] = {3, 1, 4, 1, 5, 9};
    

    然后在您的memcmp 中使用block

    如果您只想确保将内存块设置为特定值,请使用for 循环解决方案。你想出的任何其他解决方案都必须做同样的事情,检查整个块。

    如果它是一个非常大的内存块并且花费的时间太长,则另一种方法是完全删除该要求。我的意思是重新设计您的算法,使其变得不必要。假设你有一个 1G 的块。

    一个例子:不要将它们全部设置为零。相反,只需在您积极使用的前面设置位,并维护一个单独的length 变量来指示您正在使用多少。

    经过高度优化的memcmp 可能优于用户循环,但您也可能发现它没有,这仅仅是因为它必须满足一般情况 - 您检查零的特定情况可能允许编译器引入破坏memcmp 的优化。

    与所有优化一样,衡量,不要猜测!

    【讨论】:

    • 我认为 OP 想要检查给定的缓冲区是否全部设置为相同的单字节值。
    【解决方案4】:

    一种选择是从 memcmp 的源代码开始,并反复修改它以与固定缓冲区进行比较。这样,您将保留 memcmp 中内置的优化,避免外部循环的开销,并且仍然实现您的目标。你可以有如下函数:

    int memcmp2(const void *s1, size_t n1, const void *s2, size_t n2);
    

    其中 n1 是缓冲区 s1 的大小,n2 是 s2 的大小。

    【讨论】:

      【解决方案5】:

      如果您无法控制谁写入该内存块,则不可能存在允许与单个值进行有效比较的智能算法。您将需要遍历整个块,甚至不能跳过一个单词。您唯一能做的就是一次比较更多数据,可能使用可以一次处理多个单词的机器指令。

      如果您确实可以控制该内存并且只有您可以对其进行写入,那么您可以更聪明地确定其中的内容。例如,通过“浪费”一些空间来保存确定哪些字为零的位模式。例如,如果您的字是 32 位的,那么您将有一个单独的内存块,您可以在其中保存与实际内存块中的字数相同的字数。在这种情况下,这将花费您每 32 字节的可用内存 1 字节,这并不可怕。如果您确实需要字节粒度,那么成本要高得多:每 8 个 1 个。但您通常不需要那个;一旦找到未归零的单词,您就可以缩小搜索范围,并仅在该单词中搜索第一个非零字节。

      【讨论】:

        【解决方案6】:

        如果在运行 memcmp() 之后,为什么会期望内存发生变化?如果内存只属于您的进程,则不会修改它。如果这是共享内存,问题就大不相同了。

        作为替代建议,我正在考虑使用 memset() 将所有内存设置为一个已知值 - 您已经在不到 546 个滴答声中完成了。

        原因是:memset() 将一次将内存设置为已知值 - 通过同一内存进行第二次验证需要大约两倍的时间。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-07
          • 2022-08-11
          • 2021-04-24
          • 1970-01-01
          • 1970-01-01
          • 2019-12-27
          相关资源
          最近更新 更多