Ответ 1
Вот статья о том, как использовать инструкции SIMD для векторизации подсчета символов:
В общем, все, что я встречаю "по-сети" по отношению к SSE/MMX, появляется как материал математики для векторов и матрасов. Тем не менее, я ищу библиотеки оптимизированных "стандартных функций" SSE, таких как те, которые предоставляются Agner Fog или некоторые из строк на основе SSE алгоритмы сканирования в GCC.
Как быстрое общее изложение: это будут такие вещи, как memset, memcpy, strstr, memcmp BSR/BSF, т.е. stdlib-esque, созданный из SSE-интроктов
Я бы предпочел, чтобы они были для SSE1 (формально MMX2), используя intrinsics, а не сборку, но либо это нормально. надеюсь, это не слишком широкий спектр.
Обновление 1
После некоторых поисков я наткнулся на некоторые многообещающие вещи, одна библиотека привлекла мое внимание:
Я также встретил статью о нескольких векторных строковых функциях (strlen, strstr strcmp). Однако SSE4.2 выходит из моей досягаемости (как было сказано ранее, я хотел бы придерживаться SSE1/MMX).
Обновление 2
Paul R побудил меня сделать небольшой бенчмаркинг, к сожалению, поскольку мой опыт сборки сборки SSE близок к zip, я использовал кого-то другого (http://www.mindcontrol.org/~hplus/) и добавлен к нему. Все тесты (исключая оригинал, который является VC6 SP5), который скомпилирован под VC9 SP1 с полной/настраиваемой оптимизацией и /arch:SSE
on.
Первый тест был одним из моих домашних компьютеров (AMD Sempron 2200+ 512mb DDR 333), ограниченным SSE1 (таким образом, без векторизации с помощью MSVC memcpy):
comparing P-III SIMD copytest (blocksize 4096) to memcpy
calculated CPU speed: 1494.0 MHz
size SSE Cycles thru-sse memcpy Cycles thru-memcpy asm Cycles thru-asm
1 kB 2879 506.75 MB/s 4132 353.08 MB/s 2655 549.51 MB/s
2 kB 4877 598.29 MB/s 7041 414.41 MB/s 5179 563.41 MB/s
4 kB 8890 656.44 MB/s 13123 444.70 MB/s 9832 593.55 MB/s
8 kB 17413 670.28 MB/s 25128 464.48 MB/s 19403 601.53 MB/s
16 kB 34569 675.26 MB/s 48227 484.02 MB/s 38303 609.43 MB/s
32 kB 68992 676.69 MB/s 95582 488.44 MB/s 75969 614.54 MB/s
64 kB 138637 673.50 MB/s 195012 478.80 MB/s 151716 615.44 MB/s
128 kB 277678 672.52 MB/s 400484 466.30 MB/s 304670 612.94 MB/s
256 kB 565227 660.78 MB/s 906572 411.98 MB/s 618394 603.97 MB/s
512 kB 1142478 653.82 MB/s 1936657 385.70 MB/s 1380146 541.23 MB/s
1024 kB 2268244 658.64 MB/s 3989323 374.49 MB/s 2917758 512.02 MB/s
2048 kB 4556890 655.69 MB/s 8299992 359.99 MB/s 6166871 484.51 MB/s
4096 kB 9307132 642.07 MB/s 16873183 354.16 MB/s 12531689 476.86 MB/s
Вторая тестовая партия была выполнена на университетской рабочей станции (Intel E6550, 2,33 ГГц, 2 ГБ DDR2 800?)
VC9 SSE/memcpy/ASM:
comparing P-III SIMD copytest (blocksize 4096) to memcpy
calculated CPU speed: 2327.2 MHz
size SSE Cycles thru-sse memcpy Cycles thru-memcpy asm Cycles thru-asm
1 kB 392 5797.69 MB/s 434 5236.63 MB/s 420 5411.18 MB/s
2 kB 882 5153.51 MB/s 707 6429.13 MB/s 714 6366.10 MB/s
4 kB 2044 4447.55 MB/s 1218 7463.70 MB/s 1218 7463.70 MB/s
8 kB 3941 4613.44 MB/s 2170 8378.60 MB/s 2303 7894.73 MB/s
16 kB 7791 4667.33 MB/s 4130 8804.63 MB/s 4410 8245.61 MB/s
32 kB 15470 4701.12 MB/s 7959 9137.61 MB/s 8708 8351.66 MB/s
64 kB 30716 4735.40 MB/s 15638 9301.22 MB/s 17458 8331.57 MB/s
128 kB 61019 4767.45 MB/s 31136 9343.05 MB/s 35259 8250.52 MB/s
256 kB 122164 4762.53 MB/s 62307 9337.80 MB/s 72688 8004.21 MB/s
512 kB 246302 4724.36 MB/s 129577 8980.15 MB/s 142709 8153.80 MB/s
1024 kB 502572 4630.66 MB/s 332941 6989.95 MB/s 290528 8010.38 MB/s
2048 kB 1105076 4211.91 MB/s 1384908 3360.86 MB/s 662172 7029.11 MB/s
4096 kB 2815589 3306.22 MB/s 4342289 2143.79 MB/s 2172961 4284.00 MB/s
Как видно, SSE очень быстро работает на моей домашней системе, но падает на машину Intel (возможно, из-за плохого кодирования?). мой вариант сборки x86 входит на втором месте на моей домашней машине, а второй - на систему intel (но результаты выглядят немного непоследовательными, один объятия блокирует его доминирование в версии SSE1). MSVC memcpy выигрывает тесты системных систем Intel, это связано с векторизации SSE2, хотя на моей домашней машине он терпит неудачу, даже ужасный __movsd
бьет его...
ловушки: в памяти были все выровненные полномочия 2. Кэш был (надеюсь) покраснел. rdtsc использовался для синхронизации.
Интересные объекты: MSVC имеет (не включенную в любую ссылку) __ movsd intrinsic, он выводит тот же код сборки, который я использую, но он терпит неудачу в унылом состоянии (даже когда он встраивается!). Вероятно, поэтому его не внесенные в список.
VC9 memcpy может быть принудительно векторизован на моей машине, отличной от sse 2, однако он повредит стек FPU, он также, похоже, имеет ошибку.
Это полный источник для того, что я использовал для тестирования (включая мои изменения, опять же, кредит http://www.mindcontrol.org/~hplus/ для оригинала). Бинарники файлов проекта доступны по запросу.
В заключение, кажется, что вариант переключения может быть лучшим, похожим на MSVC crt one, только намного более прочным с большим количеством опций и одиночными однократными проверками (с помощью указателей функций inline'd или чего-то более коварного типа внутренний патч прямого вызова), однако встраивание, вероятно, должно было бы использовать метод наилучшего случая вместо
Обновление 3
Вопрос, заданный Эшаном, напомнил о чем-то полезном и связанном с этим, хотя и для бит-бит и бит ops, BitMagic и быть вполне полезно для больших наборов бит, у него даже есть хорошая статья на оптимизация SSE2 (бит). К сожалению, это все еще не библиотека типа CRT/stdlib esque. кажется, что большинство из этих проектов посвящено конкретному небольшому разделу (проблем).
Возникает вопрос: тогда было бы целесообразно создать проект с открытым исходным кодом, возможно многоплатформенный проект crt/stdlib, создающий различные версии стандартизованных функций, каждый из которых оптимизирован для определенной ситуации, а также " наилучшего варианта/общего использования функции с ветвлением развёртки для скаляра /MMX/SSE/SSE 2 + (à la MSVC) или сканированием времени принудительной компиляции /SIMD swich.
Это может быть полезно для HPC или проектов, где каждый бит производительности (например, игры), освобождая программиста от беспокойства о скорости встроенных функций, требует лишь небольшого количества настроек для поиска оптимального оптимизированного варианта.
Обновление 4
Я думаю, что характер этого вопроса должен быть расширен, чтобы включить методы, которые могут быть применены с использованием SSE/MMX для оптимизации для приложений, отличных от вектора/матрицы, это, вероятно, может быть использовано и для 32/64-битного скалярного кода. Хорошим примером является проверка наличия байта в заданном типе данных 32/64/128/256 бит сразу с использованием скалярных методов (бит-манипуляция), MMX и SSE/SIMD
Кроме того, я вижу много ответов по строкам "просто использую ICC", и это хороший ответ, это не мой ответ, поскольку, во-первых, ICC - это не то, что я могу использовать постоянно (если у Intel нет бесплатная версия для студентов для окон), из-за 30 проб. во-вторых, и, что более уместно, я не только после того, как библиотеки сами по себе, но и методы, используемые для оптимизации/создания функций, которые они содержат, для моего личного изменения и улучшения, и поэтому я могу применять такие методы и принципы к своему собственному коду (там, где это необходимо), в сочетании с использованием этих библиотек. надеюсь, это очистит эту часть:)
Вот статья о том, как использовать инструкции SIMD для векторизации подсчета символов:
Для простых операций, таких как memset, memcpy и т.д., когда вычислений очень мало, в оптимизации SIMD мало смысла, так как ширина полосы пропускания обычно будет ограничивающим фактором.
Может быть, libSIMDx86?
Вы можете использовать apple или OpenSolaris libc. Эти реализации libc содержат то, что вы ищете. Я искал такие вещи около 6 лет назад, и мне пришлось мучительно писать это с трудом.
Возраст назад я помню следующий конкурс кодирования под названием fastcode '. В то время они использовали некоторую потрясающую оптимизацию разбивки, используя Delphi. См. Их страницу результатов. Так как он написан в быстрой модели вызова функции Pascal (копирование аргументов в регистры), преобразование в кодовые модели функции stdc в стиле C (нажатие на стек) может быть немного неудобным. Этот проект не имеет обновлений с тех пор, как долгое время особенно не было написано кода для SSE4.2.
Solaris -> src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libc/
Apple -> www.opensource.apple.com/source/Libc/Libc-594.9.1/
Честно говоря, я бы просто установил компилятор Intel С++ и узнал различные автоматические флаги оптимизации SIMD. У нас был очень хороший опыт, оптимизирующий производительность кода, просто скомпилировав его с ICC.
Имейте в виду, что вся библиотека STL в основном является только заголовочными файлами, поэтому все это скомпилировано в вашу exe/lib/dll и, как таковое, можно оптимизировать, как вам нравится.
ICC имеет множество опций и позволяет указать (на самом простом) уровни SSE для таргетинга. Вы также можете использовать его для создания двоичного файла с несколькими путями кодов, так что если оптимальная конфигурация SSE, скомпилированная против, недоступна, она будет запускать другой набор (все еще оптимизированный) код, настроенный для менее способного процессора SIMD.
Здесь приведена быстрая реализация memcpy в C, которая может заменить стандартную библиотечную версию memcpy при необходимости:
strstr трудно оптимизировать, потому что (a)\0-term означает, что вы должны читать каждый байт в любом случае, и (б) он должен быть хорошим во всех случаях кросс.
С учетом сказанного вы можете обыграть стандартную strstr в 10 раз, используя SSE2 ops. Я заметил, что gcc 4.4 использует эти ops для strlen сейчас, но не для другие строки ops. Подробнее о том, как использовать регистры SSE2 для strlen, strchr, strpbrk и т.д. на mischasan.wordpress.com. Простите мой супер-краткий код.
#include <emmintrin.h> // Other standard #includes you can figure out...
static inline unsigned under(unsigned x)
{ return (x - 1) & ~x; }
static inline __m128i xmfill(char b)
{ return _mm_set1_epi8(b); }
static inline __m128i xmload(void const*p)
{ return _mm_load_si128((__m128i const*)p); }
static inline unsigned xmatch(__m128i a, __m128i b)
{ return _mm_movemask_epi8(_mm_cmpeq_epi8(a, b)); }
char const *sse_strstr(char const *tgt, char const *pat)
{
unsigned len = sse_strlen(pat);
if (len == 0) return tgt;
if (len == 1) return sse_strchr(tgt,*pat);
__m128i x, zero = {};
__m128i p0 = _m_set1_epi8(pat[0]), p1 = _m_set1_epi8(pat[1]);
uint16_t pair = *(uint16_t const*)pat;
unsigned z, m, f = 15 & (uintptr_t)tgt;
char const* p;
// Initial unaligned chunk of tgt:
if (f) {
z = xmatch(x = xmload(tgt - f), zero) >> f;
m = under(z) & ((xmatch(x,p0) & (xmatch(x,p1) >> 1)) >> f);
for (; m; m &= m - 1)
if (!memcmp((p = tgt+ffs(m)-1)+2, pat+2, len-2))
return p;
if (z)
return NULL;
tgt += 16 - f;
if (*(uint16_t const*)(tgt - 1) == pair
&& !memcmp(tgt+1, pat+2, len-2))
return tgt - 1;
}
// 16-byte aligned chunks of tgt:
while (!(z = xmatch(x = xmload(tgt), zero))) {
m = xmatch(x,p0) & (xmatch(x,p1) >> 1);
for (; m; m &= m - 1)
if (!memcmp((p = tgt+ffs(m)-1)+2, pat+2, len-2))
return p;
tgt += 16;
if (*(uint16_t const*)(tgt - 1) == pair && !memcmp(tgt+1, pat+2, len-2))
return tgt - 1;
}
// Final 0..15 bytes of tgt:
m = under(z) & xmatch(x,p0) & (xmatch(x,p1) >> 1);
for (; m; m &= m - 1)
if (!memcmp((p = tgt+ffs(m)-1)+2, pat+2, len-2))
return p;
return NULL;
}
Я лично не стал бы пытаться писать супер-оптимизированные версии функций libc, пытаясь обрабатывать все возможные сценарии с хорошей производительностью.
Вместо этого напишите оптимизированные версии для конкретных ситуаций, где вы достаточно знаете о проблеме, чтобы написать правильный код... и где это важно. Там есть семантическая разница между memset
и ClearLargeBufferCacheWriteThrough
.