Nano Hash - криптовалюты, майнинг, программирование

Сравните 16-байтовые строки с SSE

У меня есть 16-байтовые «строки» (они могут быть короче, но вы можете предположить, что они дополнены нулями в конце), но вы не можете предполагать, что они выровнены по 16 байтов (по крайней мере, не всегда).

Как написать процедуру, которая будет сравнивать их (на равенство) с встроенными функциями SSE? Я нашел этот фрагмент кода, который мог бы помочь, но я не уверен, подходит ли он?

register __m128i xmm0, xmm1; 
register unsigned int eax; 

xmm0 = _mm_load_epi128((__m128i*)(a)); 
xmm1 = _mm_load_epi128((__m128i*)(b)); 

xmm0 = _mm_cmpeq_epi8(xmm0, xmm1); 

eax = _mm_movemask_epi8(xmm0); 

if(eax==0xffff) //equal 
else   //not equal 

Может ли кто-нибудь объяснить это или написать тело функции?

Он должен работать в GCC / mingw (в 32-битной Windows).

13.08.2015

  • _mm_load_epi128 не существует, вы имеете в виду либо _mm_load_si128, либо, поскольку вы сказали, что они могут быть невыровненными, _mm_loadu_si128 14.08.2015

Ответы:


1

Команды сравнения векторов производят свой результат в виде маски из элементов, которые имеют значение «все единицы» (истина) или все единицы (ложь) в соответствии со сравнением между соответствующими исходными элементами.

См. https://stackoverflow.com/tags/x86/info для некоторых ссылок, которые расскажут вам, что делают эти встроенные функции.

Код в вопросе выглядит так, как будто он должен работать.

Если вы хотите узнать, какие элементы не равны, используйте версию маски перемещения (pmovmskb или movmskps). Вы можете tzcnt / bsf сканировать биты для первого совпадения или popcnt для подсчета совпадений. Все равно дает вам 0xffff маску, неравенство дает 0.


Вы можете задаться вопросом, пригодится ли здесь SSE4.1 ptest. Его можно использовать, но на самом деле он не быстрее, особенно если вы разветвляете результат вместо того, чтобы превращать его в логическое значение 0 / -1.

 // slower alternative using SSE4.1 ptest
__m128i avec, bvec;
avec = _mm_loadu_si128((__m128i*)(a)); 
bvec = _mm_loadu_si128((__m128i*)(b)); 

__m128i diff = _mm_xor_si128(avec, bvec);  // XOR: all zero only if *a==*b

if(_mm_test_all_zeros(diff, diff))  { //equal 
} else {   //not equal 
}

В asm ptest составляет 2 мопа и не может объединяться с условным переходом jcc. Таким образом, общая ветвь pxor + ptest + составляет 4 мопа для внешнего интерфейса и по-прежнему уничтожает один из входов, если у вас нет AVX для помещения результата xor в третий регистр.

pcmpeqb xmm0, xmm1 / pmovmskb eax, xmm0 / cmp/jcc - всего 3 мопа, при этом cmp / jcc сливается в 1 моп на процессорах Intel и AMD.

Если у вас есть более широкие элементы, вы можете использовать movmskps или movmskpd в результате pcmpeqd/q, чтобы получить 4-битную или 2-битную маску. Это наиболее полезно, если вы хотите сканировать бит или popcnt без деления на 4 или 8 байтов на элемент. (Или с AVX2, 8-битной или 4-битной вместо 32-битной маски.)

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

14.08.2015
  • Похоже, что инструкция ptest использует 2 мупа на текущих архитектурах Intel. Более того, он не связан с условным переходом после него. Таким образом, ваше решение генерирует 4 мопа, а код OP генерирует только 3 мопа. Дополнительные сведения см. В этом вопросе. Предположительно это означает, что ваше решение будет медленнее в жестком цикле. 27.08.2015
  • @stgatilov: Хороший улов. PTEST может иметь меньшую задержку (меньший штраф за неверный прогноз), но я думаю, что вы правы, в этом случае pcmpeq/pmovmskb/ cmp/jne меньше мопов, потому что ptest не может сравнивать-равно напрямую. Груз может складываться как в pcmpeq, так и в pxor. Я также не уверен в штрафах за неверный прогноз: я только что посмотрел на таблицы задержки Агнера Фога, а не на эксперименты. 27.08.2015
  • Я наблюдал снижение пропускной способности как в реальных тестах, так и в результатах анализа IACA после замены pmovmskb на ptest. Кажется, что инструкция ptest может быть полезна только в том случае, если вам абсолютно необходимо проверить все 128 бит регистра на ноль (что случается очень редко). Если вы проводите какое-либо сравнение (что является наиболее популярным случаем), то использование ptest после него вредно. Ну, может, у него меньшая задержка, кто знает ... 29.08.2015
  • @stgatilov: обновил свой ответ. Обратите внимание, что pcmpeq / ptest может проверять только все - не - равно. Вот почему я использовал pxor / ptest, чтобы сохранить ту же семантику, что и код OP. Однако pcmpeq должен быть таким же быстрым, как pxor на Intel. ptest работает на p0 и p5 (с задержкой 2c). pcmpeqb может работать на порту 1 или 5, поэтому он может использовать порт, которого не ptest. (pxor может работать на всех 3-х векторных портах выполнения, p0 / 1/5.) Ваш тест мог бы иметь меньше ошибочных прогнозов ветвлений, чем вы ожидали, если бы у вас была другая семантика. 29.08.2015
  • Я тестировал более сложный код (связанный с преобразованием UTF), который, как сообщает IACA, был ограничен интерфейсом. Так что для меня важны были не порты, а только количество мопов. Кроме того, ветки были типа «никогда не выполняются» или «всегда выполняются» (используются для возврата при ошибке), поэтому они были идеально предсказаны. Может быть, ptest не хуже в других случаях, но для меня сейчас очень сомнительно, можно ли его использовать. 29.08.2015
  • @stgatilov: имеет смысл. Когда я посмотрел на ptest, я подумал, что обычно они были равными, но потенциально более низкими задержками. (кроме этого случая, когда я запутался и предложил это в случае, когда это больше упс. ›.‹) 29.08.2015

  • 2

    Что ж, я не уверен, будет ли это быстрее, но это можно сделать с помощью одной встроенной инструкции SSE 4.2: проверка PCMPISTRI (Packed Compare Implicit Length Strings, Return Index) на наличие флагов переноса и / или переполнения:

    if (_mm_cmpistrc(a, b, mode))   // checks the carry flag (not set = equal)
      // equal
    else
      // unequal
    

    режим будет (для вашего случая):

    const int mode = 
      SIDD_UBYTE_OPS |         // 16-bytes per xmm
      SIDD_CMP_EQUAL_EACH |    // strcmp
      SIDD_NEGATIVE_POLARITY;  // find first different byte
    

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

    23.08.2015
  • Это не сработает, так как не будет сравнивать байты сверх нулевого байта в любом регистре. К тому же нет, быстрее бы не было. PCMPISTRI - это самая легкая строка insn SSE4.2, но она по-прежнему составляет 3 мупа (все для port0) и задержка в 11 циклов на Haswell. Insn устанавливает флаги, а также устанавливает ecx, но test/jcc по-прежнему один uop, как и только jcc. Руководство Intel insn ref документирует все до чертиков, но понимание описаний для строковых операций происходит медленно, потому что они очень сложны и имеют много режимов. stackoverflow.com/tags/x86/info содержит ссылки. 27.08.2015
  • Упс, забыл, что OP спрашивал о строках с нулевым заполнением, а не о произвольных векторах 16B. У строковых инструкций есть возможности, но они не будут такими быстрыми, как простые сравнения для проверки точного равенства. Если в OP были строки с одним нулевым байтом, а затем завершающийся мусор, строковые инструкции, вероятно, были бы лучшим выбором для строк с длиной <= 15. 27.08.2015
  • @PeterCordes: Спасибо за ваши дополнения. Я просто добавил это, чтобы показать другой способ решения этой или немного другой проблемы. --- Читая разведчика еще раз, я возражаю, что это плохо задокументировано. Интеллектуальный человек объясняет все детали, но от этого далеко не до того, чтобы извлечь из этого что-то полезное. Теперь, глядя на специалиста по разведке, я признаю, что он более подробно объясняется с примерами в главе 10. 28.08.2015
  • спасибо за подсказку документации на ch10. Я как раз смотрел Раздел 4.1 (который является частью руководства insn set ref). В нем есть блок-схема, которая показывает этапы обработки, чтобы легче было увидеть, на какой этап влияет каждый imm8 бит. Полностью согласен с тем, что переход от документации к рабочему коду с этими многофункциональными инструкциями может занять много времени! Жаль, что они все еще закодированы на микрокоде, поэтому их вряд ли стоит использовать. Думаю, это курица и яйцо; не стоит тратить кремний, чтобы сделать их быстрыми, пока не наберется много пользователей ... 28.08.2015
  • Достойный ресурс, объясняющий, как pcmpistri может работать: strchr.com/strcmp_and_strlen_using_sse_4.2 27.02.2019

  • 3

    Я постараюсь помочь с забытой Может ли кто-нибудь объяснить эту часть вопроса.

    register __m128i xmm0, xmm1; 
    register unsigned int eax; 
    

    Здесь мы объявляем несколько переменных. __m128i - это встроенный тип для целочисленных операций с регистрами SSE. Обратите внимание, что имена переменных не имеют никакого значения, но автор назвал их в точности так, как соответствующие регистры ЦП вызываются в сборке. xmm0, xmm1, xmm2, xmm3, ... все регистры для операций SSE. eax - один из регистров общего назначения.

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

    xmm0 = _mm_loadu_si128((__m128i*)(a)); 
    xmm1 = _mm_loadu_si128((__m128i*)(b)); 
    

    Этот код был изменен в соответствии с предложением @harold. Здесь мы загружаем 16 байтов из указанных указателей памяти, которые могут быть невыровненными) в переменные xmm0 и xmm1. В ассемблерном коде эти переменные, скорее всего, будут расположены непосредственно в регистрах, поэтому эти встроенные функции будут генерировать невыровненную нагрузку на память. Преобразование указателя в тип __m128i* необходимо, потому что intrinsic принимает этот тип указателя, хотя я понятия не имею, почему Intel это сделала.

    xmm0 = _mm_cmpeq_epi8(xmm0, xmm1); 
    

    Здесь мы сравниваем на равенство каждый байт из переменной xmm0 с соответствующим байтом в переменной xmm1. Суффикс _epi8 означает работу с 8-битными элементами, то есть байтами. Он чем-то похож на memcmp(&xmm0, &xmm1, 16), но дает другие результаты. Он возвращает 16-байтовое значение, которое содержит 0xFF для каждого байта с равными значениями и 0x00 для каждого байта с разными значениями.

    eax = _mm_movemask_epi8(xmm0); 
    

    Это очень важная инструкция из SSE2, которая обычно используется для написания оператора if с некоторым условием SSE. Он берет старший бит из каждого из 16 байтов в аргументе XMM и записывает их в одно 16-битное целое число. На уровне сборки этот номер находится в универсальном регистре, что позволяет нам быстро проверить его значение сразу после этого.

    if(eax==0xffff) //equal 
    else   //not equal
    

    Если все 16 байтов двух регистров XMM были равны, то _mm_cmpeq_epi8 должен возвращать маску со всеми установленными 128 битами. _mm_movemask_epi8 тогда вернет полную 16-битную маску, которая равна 0xFFFF. Если бы любые два сравниваемых байта были разными, соответствующий байт был бы заполнен нулями на _mm_cmpeq_epi8, поэтому _mm_movemask_epi8 вернет 16-битную маску с соответствующим битом не, поэтому он будет меньше 0xFFFF.

    Кроме того, вот объясненный код, заключенный в функцию:

    bool AreEqual(const char *a, const char *b) {
      __m128i xmm0, xmm1; 
      unsigned int eax; 
      xmm0 = _mm_loadu_si128((__m128i*)(a)); 
      xmm1 = _mm_loadu_si128((__m128i*)(b)); 
      xmm0 = _mm_cmpeq_epi8(xmm0, xmm1); 
      eax = _mm_movemask_epi8(xmm0); 
      return (eax == 0xffff); //equal 
    }
    
    29.08.2015
    Новые материалы

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

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

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..