确保内存区域为空的最快方式(全为NULL)?

EFC*_*EFC 8 c cocoa objective-c nsdata ios

如果我有一个unsigned char *data指针,我想检查size_t length该指针的数据是否为NULL,那么最快的方法是什么?换句话说,确保内存区域为空白的最快方法是什么?

我在iOS中实现,所以你可以假设iOS框架可用,如果有帮助的话.另一方面,简单的C方法(memcmp等)也可以.

注意,我不是要清除内存,而是试图确认它已经很清楚了(我试图找出某些位图数据中是否有任何内容,如果有帮助的话).例如,我认为以下内容可行,但我尚未尝试过:

- BOOL data:(unsigned char *)data isNullToLength:(size_t)length {
    unsigned char tester[length] = {};
    memset(tester, 0, length);
    if (memcmp(tester, data, length) != 0) {
        return NO;
    }
    return YES;
}
Run Code Online (Sandbox Code Playgroud)

我宁愿不创建一个测试器阵列,因为源数据可能非常大,我宁愿避免为测试分配内存,即使是暂时的.但我可能在那里过于保守.

更新:一些测试

感谢大家对下面的好评.我决定创建一个测试应用程序,看看它们是如何表现的,答案让我感到惊讶,所以我想我会分享它们.首先,我将向您展示我使用的算法版本(在某些情况下,它们与提议的算法略有不同),然后我将分享该领域的一些结果.

测试

首先,我创建了一些示例数据:

    size_t length = 1024 * 768;
    unsigned char *data = (unsigned char *)calloc(sizeof(unsigned char), (unsigned long)length);
    int i;
    int count;
    long check;
    int loop = 5000;
Run Code Online (Sandbox Code Playgroud)

每个测试由循环运行loop时间组成.在循环期间,一些随机数据被添加到data字节流中以及从字节流中移除.请注意,实际上没有数据添加的一半时间,因此测试不应该找到任何非零数据的一半时间.请注意,该testZeros调用是占位符,用于调用下面的测试例程.在循环之前启动计时器并在循环之后停止计时器.

    count = 0;
    for (i=0; i<loop; i++) {
        int r = random() % length;
        if (random() % 2) { data[r] = 1; }
        if (! testZeros(data, length)) {
            count++;
        }
        data[r] = 0;
    }
Run Code Online (Sandbox Code Playgroud)

测试A:nullToLength.这或多或少是我上面的原始配方,调试和简化了一下.

- (BOOL)data:(void *)data isNullToLength:(size_t)length {
    void *tester = (void *)calloc(sizeof(void), (unsigned long)length);
    int test = memcmp(tester, data, length);
    free(tester);
    return (! test);
}
Run Code Online (Sandbox Code Playgroud)

测试B:allZero.Carrotman提出的建议.

BOOL allZero (unsigned char *data, size_t length) {
    bool allZero = true;
    for (int i = 0; i < length; i++){
        if (*data++){
            allZero = false;
            break;
        }
    }
    return allZero;
}
Run Code Online (Sandbox Code Playgroud)

测试C:is_all_zero.Lundin提出的建议.

BOOL is_all_zero (unsigned char *data, size_t length)
{
    BOOL result = TRUE;
    unsigned char* end = data + length;
    unsigned char* i;

    for(i=data; i<end; i++) {
        if(*i > 0) {
            result = FALSE;
            break;
        }
    }

    return result;
}
Run Code Online (Sandbox Code Playgroud)

测试D:sumArray.这是由vladr提出的几乎重复的问题的最佳答案.

BOOL sumArray (unsigned char *data, size_t length) {
    int sum = 0;
    for (int i = 0; i < length; ++i) {
        sum |= data[i];
    }
    return (sum == 0);
}
Run Code Online (Sandbox Code Playgroud)

测试E:lulz.由Steve Jessop提出.

BOOL lulz (unsigned char *data, size_t length) {
    if (length == 0) return 1;
    if (*data) return 0;
    return memcmp(data, data+1, length-1) == 0;
}
Run Code Online (Sandbox Code Playgroud)

测试F:NSData.这是一个使用我在iOS SDK中发现的NSData对象的测试,同时处理所有这些.事实证明,Apple确实知道如何比较设计为独立于硬件的字节流.

- (BOOL)nsdTestData: (NSData *)nsdData length: (NSUInteger)length {
    void *tester = (void *)calloc(sizeof(void), (unsigned long)length);
    NSData *nsdTester = [NSData dataWithBytesNoCopy:tester length:(NSUInteger)length freeWhenDone:NO];
    int test = [nsdData isEqualToData:nsdTester];
    free(tester);
    return (test);
}
Run Code Online (Sandbox Code Playgroud)

结果

那么这些方法是如何比较的?这里有两组数据,每组代表5000个循环检查.首先,我在相对较旧的iMac上运行的iPhone模拟器上尝试了这个,然后我尝试在第一代iPad上运行.

在iMac上运行的iPhone 4.3模拟器上:

// Test A, nullToLength:  0.727 seconds
// Test F, NSData:        0.727
// Test E, lulz:          0.735
// Test C, is_all_zero:   7.340
// Test B, allZero:       8.736
// Test D, sumArray:     13.995
Run Code Online (Sandbox Code Playgroud)

在第一代iPad上:

// Test A, nullToLength: 21.770 seconds
// Test F, NSData:       22.184
// Test E, lulz:         26.036
// Test C, is_all_zero:  54.747
// Test B, allZero:      63.185
// Test D, sumArray:     84.014
Run Code Online (Sandbox Code Playgroud)

这些只是两个样本,我进行了多次测试,结果略有不同.性能的顺序总是一样的:A&F非常接近,E就在后面,C,B和D.我会说A,F和E是虚拟联系,在iOS上我更喜欢F因为它利用Apple对处理器更改问题的保护,但A&E非常接近.memcmp方法显然胜过简单的循环方法,在模拟器中接近十倍,在设备本身上快两倍.奇怪的是,D,来自另一个线程的获胜答案在这个测试中表现得非常糟糕,可能是因为当它碰到第一个差异时它不会突破循环.

Car*_*n42 2

不确定这是否是最好的,但我可能会这样做:

bool allZero = true;
for (int i = 0; i < size_t; i++){
    if (*data++){
        //Roll back so data points to the non-zero char
        data--;
        //Do whatever is needed if it isn't zero.
        allZero = false;
        break;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您刚刚分配了该内存,则始终可以调用 calloc 而不是 malloc(calloc 要求所有数据都清零)。(编辑:阅读您对第一篇文章的评论,您实际上并不需要这个。我将保留它以防万一)

  • @DarkDust:尝试优化的有效性还取决于输入通常是否全为 0,以及如果不是第一个非零字节所在的位置。显然,如果您对 1GB 内存进行“或”运算,但第一个区别在于第二个字节,那么再多的 SIMD 也无济于事。基本上输入越大,尽早退出越好,因此您的建议的改进是使用两个循环 - 正如您所描述的那样,一个内部循环一次执行中等大小的数据块,然后是一个外部循环可以提前退出。 (2认同)