Самый быстрый способ убедиться, что область памяти пуста (все NULL)? - PullRequest
8 голосов
/ 01 июля 2011

Если у меня есть указатель 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;
}

Я бы не хотел создавать массив тестеров, потому что исходные данные могут быть довольно большими, и я быскорее избегайте выделения памяти для теста, даже временно.Но я могу просто быть слишком консервативным там.

ОБНОВЛЕНИЕ: некоторые тесты

Спасибо всем за отличные ответы ниже.Я решил создать тестовое приложение, чтобы увидеть, как они работают, ответы удивили меня, поэтому я решил поделиться ими.Сначала я покажу вам версию алгоритмов, которые я использовал (в некоторых случаях они немного отличаются от предложенных), а затем я поделюсь некоторыми результатами из этой области.

Тесты

Сначала я создал несколько примеров данных:

    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;

Каждый тест состоял из цикла, выполненного 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;
    }

Тест 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);
}

Тест 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;
}

Тест C: is_all_zero.Предложено Лундином.

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;
}

Тест 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);
}

Тест E: lulz.Предложено Стивом Джессопом.

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;
}

Тест F: NSData.Это тест с использованием объекта NSData, который я обнаружил в iOS SDK, работая над всем этим.Оказывается, у 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);
}

Результаты

Так как же сравнивались эти подходы?Вот два набора данных, каждый из которых представляет 5000 циклов проверки.Сначала я попробовал это на iPhone Simulator, работающем на относительно старом iMac, затем я попробовал это на iPad первого поколения.

На iPhone 4.3 Simulator, работающем на iMac:

// 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

На 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

Это всего лишь два образца, я много раз проходил тест с незначительно отличающимися результатами.Порядок производительности всегда был одинаковым: A & F очень близко, E позади, C, B и D. Я бы сказал, что A, F и E - это виртуальные связи, в iOS я бы предпочел F, потому что этоиспользует защиту Apple от проблем с заменой процессора, но A & E очень близки.Подход с использованием memcmp явно выигрывает у простого подхода с использованием петель, почти в десять раз быстрее в симуляторе и в два раза быстрее на самом устройстве.Как ни странно, D, победивший ответ из другого потока показал себя очень плохо в этом тесте, возможно, потому, что он не вырвался из цикла при достижении первого различия.

Ответы [ 6 ]

3 голосов
/ 01 июля 2011

Я думаю, вы должны сделать это с явным циклом, но только для lulz:

if (length == 0) return 1;
if (*pdata) return 0;
return memcmp(pdata, pdata+1, length-1) == 0;

В отличие от memcpy, memcmp не требует, чтобы два раздела данных не перекрывались.1006 *

Это может быть намного медленнее, чем цикл, хотя, поскольку неравномерность входных указателей означает, что реализация memcmp, вероятно, не так уж и полезна для оптимизации, плюс она скорее сравнивает память с памятью.чем память с константой.Достаточно легко профилировать и узнавать.

2 голосов
/ 01 июля 2011

Не уверен, что это лучше, но я, вероятно, сделал бы что-то вроде этого:

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;
    }
}

Если вы только что выделили эту память, вы всегда можете вызвать calloc, а не malloc (calloc требуетданные обнуляются).(Изменить: читая ваш комментарий к первому сообщению, вам это на самом деле не нужно. Я просто оставлю это на всякий случай)

2 голосов
/ 01 июля 2011

Если вы выделяете память самостоятельно, я бы предложил использовать функцию calloc().Это похоже на malloc(), только сначала он обнуляет буфер.Это то, что используется для выделения памяти для объектов Objective C и является причиной того, что все ivars по умолчанию имеют значение 0.

С другой стороны, если это статически объявленный буфер или буфер, который вы не выделяете самостоятельно, memset() это простой способ сделать это.

1 голос
/ 01 июля 2011

Это был бы предпочтительный способ сделать это в C:

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

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

  return result;
}

(Хотя обратите внимание, что строго и формально говоря, ячейка памяти, содержащая указатель NULL, не обязательно должна быть 0, поскольку приведение нулевого указателя приводит к нулевому значению, а приведение нуля к указателю приводит указатель NULL. На практике это не должно иметь значения, так как все известные компиляторы используют 0 или (void *) 0 для NULL.)

1 голос
/ 01 июля 2011

Логика для получения значения, его проверки и установки будет стоить как минимум дороже, чем просто установить его. Вы хотите, чтобы он был нулевым, поэтому просто установите его в null с помощью memset ().

0 голосов
/ 02 июля 2011

Обратите внимание на изменение первоначального вопроса выше.Я провел несколько тестов, и стало ясно, что подход memcmp или использование объекта NSData от Apple и его метода isEqualToData: являются лучшими подходами к скорости.Простые петли мне понятнее, но медленнее на устройстве.

...