Самый быстрый способ видеть, сколько байтов равно между массивами фиксированной длины

Я использовал это в прошлом, и он неплохо работает. Это не бесплатно, но вы должны обязательно взглянуть. JavaScript Obfuscator & amp; Кодер

11
задан Rodrigo Gómez 22 September 2008 в 21:56
поделиться

15 ответов

ОБНОВЛЕНИЕ: Этот ответ был изменен, чтобы заставить мои комментарии соответствовать исходному коду, обеспеченному ниже.

Существует оптимизация, доступная, если у Вас есть возможность использовать SSE2 и popcnt инструкции.

16 байтов, оказывается, подходят приятно к регистру SSE. Используя C++ и assembly/intrinsics, загрузитесь два 16 массивов байтов в регистры xmm и cmp их. Это генерирует битовую маску, представляющую истинное/ложное условие сравнивания. Вы затем используете movmsk инструкцию загрузить немного представления битовой маски в регистр x86; это затем становится небольшим полем, где можно рассчитать весь 1's для определения, сколько истинных значений Вы имели. Аппаратные средства popcnt инструкция могут быть быстрым способом рассчитать весь 1's в регистре.

Это требует знания assembly/intrinsics и SSE в частности. Необходимо смочь найти веб-ресурсы для обоих.

При выполнении этого кода машины, которая не поддерживает или SSE2 или popcnt, необходимо затем выполнить итерации через массивы и считать различия с развернутым подходом цикла.

Удачи

Править: Так как Вы указали, что не знали блок, вот некоторый пример кода для иллюстрирования моего ответа:

#include "stdafx.h"
#include <iostream>
#include "intrin.h"

inline unsigned cmpArray16( char (&arr1)[16], char (&arr2)[16] )
{
    __m128i first = _mm_loadu_si128( reinterpret_cast<__m128i*>( &arr1 ) );
    __m128i second = _mm_loadu_si128( reinterpret_cast<__m128i*>( &arr2 ) );

    return _mm_movemask_epi8( _mm_cmpeq_epi8( first, second ) );
}

int _tmain( int argc, _TCHAR* argv[] )
{
    unsigned count = 0;
    char    arr1[16] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0 };
    char    arr2[16] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0 };

    count = __popcnt( cmpArray16( arr1, arr2 ) );

    std::cout << "The number of equivalent bytes = " << count << std::endl;

    return 0;
}

Некоторые примечания: Эта функция использует инструкции SSE2 и popcnt инструкцию, представленную в процессоре Phenom (это - машина, которую я использую). Я полагаю, что новые процессоры Intel с SSE4 также имеют popcnt. Эта функция не проверяет на поддержку инструкции с CPUID; функция не определена, если используется на процессоре, который не имеет SSE2 или popcnt (Вы, вероятно, получите недопустимую инструкцию по коду операции). Тот код обнаружения является отдельным потоком.

Я не синхронизировал этот код; причина я думаю, что это быстрее, состоит в том, потому что это сравнивает 16 байтов за один раз, без веток. Необходимо изменить это для установки среде, и время этому сами, чтобы видеть, работает ли это на Вас. Я записал и протестировал это на VS2008 SP1.

SSE предпочитает данные, которые являются выровненные на естественной 16-байтовой границе; если можно гарантировать, что затем необходимо получить дополнительные улучшения скорости, и можно изменить _mm_loadu_si128 инструкции на _mm_load_si128, который требует выравнивания.

16
ответ дан 3 December 2019 в 02:21
поделиться

Одна дополнительная возможная оптимизация: если Вы ожидаете, что большую часть времени массивы идентичны затем, это могло бы быть немного быстрее, чтобы сделать memcmp () как первый шаг, установка '16' как ответ, если тест возвращает true. Если курс, если бы Вы не ожидаете, что массивы будут идентичны очень часто, который только замедлил бы вещи.

0
ответ дан 3 December 2019 в 02:21
поделиться

Всегда существует старая добрая инструкция x86 REPNE CMPS.

0
ответ дан 3 December 2019 в 02:21
поделиться

Есть ли какой-либо способ, которым можно изменить способ, которым хранятся массивы? Сравнение 1 байта за один раз является чрезвычайно медленным рассмотрением, что Вы, вероятно, используете 32-разрядный компилятор. Вместо этого при хранении 16 байтов в 4 (32-разрядных) целых числах или 2 (64-разрядных) longs необходимо было бы только выполнить 4 или 2 сравнения соответственно.

Вопрос спросить себя состоит в том, сколько стоимость того, чтобы хранить данные как или 2-долгие массивы с 4 целыми числами. Как часто делают необходимо получить доступ к данным и т.д.

0
ответ дан 3 December 2019 в 02:21
поделиться

Попытайтесь использовать указатели вместо массивов:

p1 = &array1[0];
p2 = &array2[0];
match += (*p1++ == *p2++);
// copy 15 times.

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

И действительно ли Вы уверены, что эта стандартная программа является узким местом в Вашей обработке? Вы на самом деле ускоряете производительность своего приложения в целом путем оптимизации этого? Снова, только измерение скажет.

0
ответ дан 3 December 2019 в 02:21
поделиться

Если запись, которая 16 раз быстрее, чем простой цикл, то Ваш компилятор или сосет или Вам не включили оптимизацию.

Короткий ответ: нет никакого более быстрого пути, если Вы действительно не векторизовали операции на параллельных аппаратных средствах.

0
ответ дан 3 December 2019 в 02:21
поделиться

Это быстрее как один оператор?

matches += (array1[0] == array2[0]) + (array1[1] == array2[1]) + ...;
0
ответ дан 3 December 2019 в 02:21
поделиться

Если Вы объясняете, что данные на самом деле представляют затем мог бы быть полностью другой способ представить данные в памяти, которая заставит этот тип грубой силы выдержать сравнение ненужный. Хотите уточнить то, что на самом деле представляют данные??

1
ответ дан 3 December 2019 в 02:21
поделиться

Волшебные параметры компилятора будут варьироваться время значительно. В особенности то, чтобы заставлять это генерировать векторизацию SSE, вероятно, получит Вас огромное ускорение.

1
ответ дан 3 December 2019 в 02:21
поделиться

Это должно быть независимо от платформы, или это кодирует, всегда работает на том же типе ЦП? При ограничении себя современными x86 центральными процессорами Вы можете использовать инструкции MMX, которые должны позволить Вам воздействовать на массив 8 байтов в одном такте системных часов. AFAIK, gcc позволяет Вам встраивать блок в свой код C, и компилятор Intel (ICC) поддерживает intrinsics, которые являются обертками, которые позволяют Вам называть определенные инструкции по сборке непосредственно. Другие системы команд SIMD, такие как SSE, могут также быть полезны для этого.

1
ответ дан 3 December 2019 в 02:21
поделиться

Если соответствия являются общим падежом, затем пытаются загрузить значения как 32 бита ints вместо 16, таким образом, можно выдержать сравнение 2 сразу (и считать его как 2 соответствия).

Если два 32 битовых значения не являются тем же затем, необходимо будет протестировать их отдельно (И вершина и нижняя часть 16 битовых значений).

Код будет более сложным, но должен быть быстрее.

При предназначении для 64-разрядной системы, Вы могли бы сделать тот же прием с 64 битами ints, и если бы Вы действительно хотите раздвинуть границы, затем смотрят на заскакивание в ассемблер и использование различных основанных на векторе инструкций, которые позволили бы Вам работать с 128 битами сразу.

2
ответ дан 3 December 2019 в 02:21
поделиться

Если бы Вам нужно абсолютное самое низкое место, я пошел бы с ассемблерным кодом. Я не выполнил в этом некоторое время, но я буду держать пари, что MMX (или более вероятно SSE2/3) имеет инструкции, которые могут позволить Вам сделать точно это в очень немногих инструкциях.

2
ответ дан 3 December 2019 в 02:21
поделиться

Если у Вас есть способность управлять местоположением массивов, исправляя один после другого в памяти, например, это могло бы заставить их быть загруженными в кэш ЦП на первом доступе.

Это зависит от ЦП и его структуры кэша и будет варьироваться от одной машины до другого.

Можно читать об иерархии памяти и кэше в Henessy & Patterson's Computer Architecture: Количественный Подход

2
ответ дан 3 December 2019 в 02:21
поделиться

Ключ должен сделать сравнения с помощью самого большого регистра поддержки ЦП, затем нейтрализация к байтам при необходимости.

Ниже кода демонстрирует с использованием 4-байтовых целых чисел, но если Вы работаете на архитектуре SIMD (любой современный Intel или микропроцессор AMD), Вы могли бы сравнить оба массива в одной инструкции перед отступанием к основанному на целом числе циклу. Большинство компиляторов в эти дни имеет внутреннюю поддержку 128-разрядных типов, так НЕ потребует ASM.

(Обратите внимание, что для сравнений SIMD Ваши массивы должны были бы составить 16 байтов, выровненных, и некоторые процессоры (например, MIPS) потребуют, чтобы массивы составили 4 байта, выровненные для основанных на интервале сравнений.

Например.

int* array1 = (int*)byteArray[0];
int* array2 = (int*)byteArray[1];

int same = 0;

for (int i = 0; i < 4; i++)
{
  // test as an int
  if (array1[i] == array2[i])
  {
    same += 4;
  }
  else
  {
    // test individual bytes
    char* bytes1 = (char*)(array1+i);
    char* bytes2 = (char*)(array2+i);

    for (int j = 0; j < 4; j++)
    {
      same += (bytes1[j] == bytes2[j];
    }
  }
}

Я не могу помнить то, что точно компилятор MSVC поддерживает для SIMD, но Вы могли сделать что-то как;

// depending on compiler you may have to insert the words via an intrinsic
__m128 qw1 = *(__m128*)byteArray[0];
__m128 qw2 = *(__m128*)byteArray[1];

// again, depending on the compiler the comparision may have to be done via an intrinsic
if (qw1 == qw2)
{
    same = 16;
}
else
{
    // do int/byte testing
}
7
ответ дан 3 December 2019 в 02:21
поделиться

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

1
ответ дан 3 December 2019 в 02:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: