Comments 91
если смотреть по циклам (в тонком месте), __rdtsc() замерийте с ней и сами увидите ) у массивов есть нюанс с выделением памяти получение значения по индексу разве ускоряется?
double time = __rdtsc();
//1
time = __rdtsc() - time;
printf("%12s: %.2f cycles\n", "", time*0.001f);//для 1 итерации я думаю будет 0.1f а может 1f
^ 1000 итераций
//на милионе операций 0.000001f у меня +- нужные значения
Статья по факту про simd, тут ускорение за счёт нескольких операций за раз получается.
Без векторов сама по себе операция чтения по индексу у обоих одинаковая выходит.
понимаю, автовекторизация, я пытался такой участок кидать в симд
dst[i] = src[i] + n; // префиксные суммы такого же вида почти что
у меня -50% производительности, но зато сделал сдвиги как понимал ), а код С практически в 0 делает на локалке эту сумму без симда
я склеивал старший и младший разряды) двигал их от влево, а на верху складывал 256 регистры ) дальше я оставил это пока до лучших времен )
получилось ускорить только матрицы по векторизации, остальное тоже автовекторизация
То есть, руками векторизовали этот кусок - `dst[i] = src[i] + n;`, и в нём пессимизация случилась? А можно на весь цикл посмотреть?
да я наивно реализовал, тоесть 8 числами двигал видимо в этом и проблема, но то я на тот не смотрел никуда, писал как понимал
Скрытый текст
__m256 HLZO256(__m256 y)
{
__m128 xh1=_mm256_extractf128_ps(y,0);
__m128 xl1=_mm256_extractf128_ps(y,1);
return _mm256_set_m128(xh1,xl1);//16V 16
}
__m256 shiftinGHLZO25616(__m256 y)
{
__m256 x41=HLZO256(y);
return _mm256_castsi256_ps(_mm256_alignr_epi8(_mm256_castps_si256(_mm256_permute2f128_ps(x41, x41, 0x03)),_mm256_castps_si256( x41), 16));
}
__m256 shiftinGHLZO25612(__m256 y)
{
__m256 x41=HLZO256(y);
return _mm256_castsi256_ps(_mm256_alignr_epi8(_mm256_castps_si256(_mm256_permute2f128_ps(x41, x41, 0x03)),_mm256_castps_si256( x41), 12));
}
__m256 shiftinGHLZO2568(__m256 y)
{
__m256 x41=HLZO256(y);
return _mm256_castsi256_ps(_mm256_alignr_epi8(_mm256_castps_si256(_mm256_permute2f128_ps(x41, x41, 0x03)),_mm256_castps_si256( x41), 8));
}
__m256 shiftinGHLZO2564(__m256 y)
{
__m256 x41=HLZO256(y);
return _mm256_castsi256_ps(_mm256_alignr_epi8(_mm256_castps_si256(_mm256_permute2f128_ps(x41, x41, 0x03)),_mm256_castps_si256( x41), 4));
}
__m256 shiftinGHLZO2560(__m256 y)
{
__m256 x41=HLZO256(y);
return _mm256_castsi256_ps(_mm256_alignr_epi8(_mm256_castps_si256(_mm256_permute2f128_ps(x41, x41, 0x03)),_mm256_castps_si256( x41), 0));
}
__m256 LHOZ256(__m256 y)
{
__m128 xh1=_mm256_extractf128_ps(y,1);
__m128 xl1=_mm256_extractf128_ps(y,0);
return _mm256_set_m128(xh1,xl1);//16V 16
}
__m256 shiftinGLHOZ25616(__m256 y)
{
__m256 x41=LHOZ256(y);
return _mm256_castsi256_ps(_mm256_alignr_epi8(_mm256_castps_si256(_mm256_permute2f128_ps(x41, x41, 0x03)),_mm256_castps_si256( x41), 16));
}
__m256 shiftinGLHOZ25612(__m256 y)
{
__m256 x41=LHOZ256(y);
return _mm256_castsi256_ps(_mm256_alignr_epi8(_mm256_castps_si256(_mm256_permute2f128_ps(x41, x41, 0x03)),_mm256_castps_si256( x41), 12));
}
__m256 shiftinGLHOZ2568(__m256 y)
{
__m256 x41=LHOZ256(y);
return _mm256_castsi256_ps(_mm256_alignr_epi8(_mm256_castps_si256(_mm256_permute2f128_ps(x41, x41, 0x03)),_mm256_castps_si256( x41), 8));
}
__m256 shiftinGLHOZ2564(__m256 y)
{
__m256 x41=LHOZ256(y);
return _mm256_castsi256_ps(_mm256_alignr_epi8(_mm256_castps_si256(_mm256_permute2f128_ps(x41, x41, 0x03)),_mm256_castps_si256( x41), 4));
}
__m256 shiftinGLHOZ2560(__m256 y)
{
__m256 x41=LHOZ256(y);
return _mm256_castsi256_ps(_mm256_alignr_epi8(_mm256_castps_si256(_mm256_permute2f128_ps(x41, x41, 0x03)),_mm256_castps_si256( x41), 0));
}
__m256 aplusb_aligned(float *c,float *a,float *b,int I,int L) {
__m256 y = _mm256_load_ps( (&b[I]));
__m256 x41;
__m256 mask;
switch(L){
case 1:
mask = _mm256_set_ps(1,1,1,1,1,1,1,1);
x41= shiftinGHLZO25616(y);
x41= _mm256_mul_ps(x41,mask);
// WTSGRB(v,x41,0);
return x41;
break;
case 2:
x41= shiftinGHLZO25612(y);
mask= _mm256_set_ps(1,1,1,1,1,1,1,0);
x41= _mm256_mul_ps(x41,mask);
// WTSGRB(v,x41,1);
return x41;
break;
case 3:
x41= shiftinGHLZO2568(y);
mask= _mm256_set_ps(1,1,1,1,1,1,0,0);
x41= _mm256_mul_ps(x41,mask);
// WTSGRB(v,x41,2);
return x41;
case 4:
x41= shiftinGHLZO2564(y);
mask= _mm256_set_ps(1,1,1,1,1,0,0,0);
x41= _mm256_mul_ps(x41,mask);
// WTSGRB(v,x41,3);
return x41;
break;
case 5:
x41= shiftinGHLZO2560(y);
mask= _mm256_set_ps(1,1,1,1,0,0,0,0);
x41= _mm256_mul_ps(x41,mask);
// WTSGRB(v,x41,4);
return x41;
break;
case 6:
x41= shiftinGLHOZ25612(y);
mask= _mm256_set_ps(1,1,1,0,0,0,0,0);
x41= _mm256_mul_ps(x41,mask);
// WTSGRB(v,x41,5);
return x41;
break;
case 7:
x41= shiftinGLHOZ2568(y);
mask= _mm256_set_ps(1,1,0,0,0,0,0,0);
x41= _mm256_mul_ps(x41,mask);
// WTSGRB(v,x41,6);
return x41;
break;
case 8:
x41= shiftinGLHOZ2564(y);
mask= _mm256_set_ps(1,0,0,0,0,0,0,0);
x41= _mm256_mul_ps(x41,mask);
// WTSGRB(v,x41,7);
return x41;
break;
}
return y;
}
void fillPrefixSum1(float *a)
{
for (int i = 0; i < 16; i+=8){
__m256 x = _mm256_load_ps( (&a[i]));
if(i>0)
x = _mm256_add_ps(_mm256_add_ps(x,aplusb_aligned(c,a,a,i,2)),_mm256_set1_ps(c[i-1]));
else
x = _mm256_add_ps(x,aplusb_aligned(c,a,a,i,2));
x = _mm256_add_ps(x,aplusb_aligned(c,a,a,i,3));
x = _mm256_add_ps(x,aplusb_aligned(c,a,a,i,4));
x = _mm256_add_ps(x,aplusb_aligned(c,a,a,i,5));
x = _mm256_add_ps(x,aplusb_aligned(c,a,a,i,6));
x = _mm256_add_ps(x,aplusb_aligned(c,a,a,i,7));
x = _mm256_add_ps(x,aplusb_aligned(c,a,a,i,8));
_mm256_store_ps(&c[i],x);//
}
}
по скорости как я понял бред, но я вставляю числа на сколько могу проверить считает, касты тут для gcc, ну и тут плавающие точки
Вы что, пытаетесь через simd ускорить подсчет префиксных сумм? Не получится. Потому что каждое следующее значение зависит от предыдущего. Вы не можете их вычислить быстрее чем за n разных операций. Навешивая тут сверху симд, вы все только замедляете. У вас все те же n операций, но теперь тяжелее. симд ускаряет за счет того, что вы далете в 4-8-16 раз меньше операций, когда их можно выполнять параллельно. Если у вас зависимость по данным, то операции нельзя распараллелить.
P.s. Как префиксные суммы связанны со статьей?
покажите примеры пожалуйста, еще с замерами времени пожалуйста
https://godbolt.org/z/6q6broYsE - мой пример
докажите обратное prefix/
Скрытый текст

пожалуйста
только вы покажите 16 чисел не от нуля а 16 рандомных хотябы
dst[i] = src[i] + n; // префиксные суммы такого же вида почти что докажите обратное пожалуйста, в связи с повторением инцидента с вашей стороны(вы уже что-то пытались мне доказать), я если не покажите примеры и замеры о чем вы говорите перестану вам отвечать, простите
если убрать вывод в консоль и домножить time на 0.16f потомучто цикл до 16 то 38 циклов, а как вы предлагаете замерить?, ведь надо оценить обьективно, а не теоретически
Примеры чего? Ускорения? Так я говорю, что оно не возможно. И вы сами говорите, что у вас замедление.
префиксные суммы такого же вида почт
Только тем, что и там и там есть сложение. В перфиксных суммах результат каждого сложения зависит от предыдущего результата. В статье же - нет (кроме случая пересекающихся массивов. Там тоже есть зависимость и поэтому компилятор отказывается это векторизовывать. О чем и статья, собственно).
вы ошибаетесь, лучше не минусите а разберитесь
компилятор векторизирует успешно такого толка вещи
Скрытый текст
precalc://pref[i] = pref[i - 1] + a[i];15-30 циклов
vmovss xmm1, DWORD PTR a[rip]
vaddss xmm8, xmm1, DWORD PTR a[rip+4]
vaddss xmm4, xmm8, DWORD PTR a[rip+8]
vaddss xmm9, xmm4, DWORD PTR a[rip+12]
vaddss xmm2, xmm9, DWORD PTR a[rip+16]
vaddss xmm10, xmm2, DWORD PTR a[rip+20]
vaddss xmm5, xmm10, DWORD PTR a[rip+24]
vaddss xmm11, xmm5, DWORD PTR a[rip+28]
vunpcklps xmm1, xmm1, xmm8
vaddss xmm0, xmm11, DWORD PTR a[rip+32]
vaddss xmm12, xmm0, DWORD PTR a[rip+36]
vunpcklps xmm4, xmm4, xmm9
vaddss xmm6, xmm12, DWORD PTR a[rip+40]
vunpcklps xmm2, xmm2, xmm10
vmovlhps xmm1, xmm1, xmm4
vaddss xmm13, xmm6, DWORD PTR a[rip+44]
vaddss xmm3, xmm13, DWORD PTR a[rip+48]
vunpcklps xmm5, xmm5, xmm11
vaddss xmm14, xmm3, DWORD PTR a[rip+52]
vunpcklps xmm0, xmm0, xmm12
vmovlhps xmm2, xmm2, xmm5
vaddss xmm7, xmm14, DWORD PTR a[rip+56]
vaddss xmm15, xmm7, DWORD PTR a[rip+60]
vinsertf128 ymm1, ymm1, xmm2, 0x1
vunpcklps xmm6, xmm6, xmm13
vmovaps YMMWORD PTR pref[rip], ymm1
vunpcklps xmm3, xmm3, xmm14
vmovlhps xmm0, xmm0, xmm6
vunpcklps xmm7, xmm7, xmm15
vmovlhps xmm3, xmm3, xmm7
vinsertf128 ymm0, ymm0, xmm3, 0x1
vmovaps YMMWORD PTR pref[rip+32], ymm0
vzeroupper
ret
.LC8:
давайте обьективнее пожалуйста, у меня не всегда лучше по циклам, но до 2 раз медленнее и это нормально - в том смысле что это наглядно происходит, а не теоретически, вопрос возможности реален вывод выше
Дайте ссылочку на godbolt с исходным кодом и я принесу публичные извинения.
https://godbolt.org/z/7WPYWo9re вот оба примера хоть до 4096 можно прогнать
Ну блин, вы почитайте ваш ассемблерный выход-то. Там нет никакой векторизации вычислений, ровно как я и говорил. Посчитайте, сколько у вас там операций vaddss. 15. Сколько операций сложения в исходном коде? 15. Сколько их выполнено параллельно через Single Instruction Multiple Data? Ровно 0.
Хmm регистры используются при сложении, просто потому что это float. Ну, удобнее компилятору использовать эти регистры вместо вещественного "сопроцессора" (он не сопроцессор уже давно, но концепция стека там осталась). Ну не завезли в X86 вещественных скалярных регистров.
Чуть проще понять, что там происходит, если сделать массив из 4 элементов: godbolt.
Смотрите,там 4 операции загрузки данных в регистры. Каждый из 4-рех исходных элементов попадает в свой отдельный регистр (movss). Потом там 3 операции сложения (addss). Ровно столько, сколько в исходном коде. Только запись в память оптимизирована одной выгрузкой, это да.
Поменяйте float на int, компилятор будет для счета использовать скалярные регистры, ибо никакой векторизации тут нет. Запись последовательных вычисленных значений одной операцией будет, это да, но вычисления не векторизованы - все те же 3 операции сложения.
Так что можете не пытаться сложения векторизовывать.
4096 елементов у вас суммируется?
у меня вот считается я даже по своему примеру сверяюсь
sse вот еще ссылка тип операции в разделе Трудности автовекторизации a[i]=b[i]+c[i]
если есть пример значит в симд можно придти к такому же результату, может вы пришли к такому, если пришли к такому же поздравляю, матрицы тоже не паралелятся прям, считается и пишет, однако прирост есть по циклам наглядный, векторизацию можно применять, почему это нет. тогда по вашей теории вы скомпилируете код и у вас упадёт производительность, мой локальный хост показывает обратное
4096 елементов у вас суммируется?
Суммируются. Циклом, в 4096 сложений, даже без разворачивания цикла. Никакой векторизации.
матрицы тоже не паралелятся прям
Матрицы как раз параллелятся элементарно. У вас n^2 выходных значений, каждое из которых считается независимо от всех остальных. Поэтому их можно считать параллельно. И вместо N^3 сложений у вас получается N^3/4 четверок сложений - вот и ускорение в 4 раза.
sse вот еще ссылка тип операции в разделе Трудности автовекторизации a[i]=b[i]+c[i]
Нет, проблема с пересекающимеся данными тут не применима, ибо я взял ваш пример с двумя глобальными массивами. Даже с допиской #pragma GCC ivdep
ничего не меняется.
пример значит в симд можно придти к такому же результату,
Да, симд-ом можно всегда получить такой же ответ. Но иногда, как в примере с префиксными суммами он получается недоутилизорован. Кажая операция по факту вместо четырех значений работает только с одним. Никакого прироста скорости это не дает.
Вы понимаете, что Simd ускоряет вычисления не потому, что вот эти xmm регистры такие волшебные, а потому что там происходит по 4 операции сразу?
вы не мне доказывайте а гигафлопсам, которые на скрине, и людям на стаковерфлоу там есть вопросы тоже почитайте, ваша теория не работает против практики извините
тем более вы сами написали, какойто пример если он даёт до 40 циклов это уже разница вы говорите одно на деле другое, поэтому спорить чтобы спорить я не намерен, извините я с вами не продолжу дальнейшее по симдам, тенденцию даже вы сами показали - надеюсь там 40 циклов, если так то обсуждать нечего, если там 40 циклов вы сами теорию свою опровергли - если там 40 циклов то вам нет смысла что-то доказывать, вы поспорили и сами пример показали если там 40 циклов, и это нюанс я считаю
Вы что, пытаетесь через simd ускорить подсчет префиксных сумм?
Нет, здесь не алгоритм std::partial_sum
, где каждое следующее значение зависит от предыдущего, как CRC32 какой-нибудь, и впихнуть SIMD не получится.
Функция transform()
в этой статье делает операции независимо от предыдущих, и может быть полностью выполняться параллельно.
паралелизм на уровне данных ) тоесть даже матрицы не паралельно вычисляются )
в самом коде даже это видно откройте любой симд код перемножения матриц там нет парелела )
по одной из сылок она тут указана там человек на тест по Blas включал 2 потока как он пишет) а в самом перемножении не вижу паралельности последовательность вижу паралельность нет, в черном углу черную кошку тоже не вижу
Я не про эту эту статью, я про код этого комментатора.
так вы же опровергли себя в чем ваши вопросы что вы хотите?
Понятия не имею о чем вы (в прочем, это частое явление с вашими комментариями, и не только у меня).
ну вы спорите о симде, в котором нету паралельности только последовательности, и о какомто транспонировании указывали, которого там нету, вдруг вы ошибаетесь, а сегодня опять стали спорить и даже сделали лучше как я понимаю, тоесть доказали что можно улучшить ) тоесть опровергли себя )
еще бы понять что вы имели ввиду под транспонированием )
так вы выше писали что паралелится так продемонстрируйте как вы увидели что паралелится, в коде последовательно идёт расчет
и в матрицах и в сумме с 1 числом, и в префиксных суммах)
где вы там паралельности увидели покажите пожалуйста )
вы вот второй день такие вопросы задаёте, а у меня крутейший стек по итогу получился )
так вы выше писали что паралелится так продемонстрируйте как вы увидели что паралелится,
Наводящий вопрос: как расшифровывается абревиатура SIMD, знаете?
суть всё равно не совсем в этом, а примениить знания хотябы в ускоренной структуре данных
лучше скажите почему С++ код не весь векторизируется, а С практически весь, моё приложение на С почти все векторизированное
у меня даже на хосте префиксы мои чуть быстрее чем
for (int i = 1; i < 4096; i++)c[i] = c[i - 1] + a[i];
так можно ничего не улучшать и всё в сумме по чуть чуть будет замедлять ) такое может быть?)
вот вы доказали что не надо векторизировать, что так и так улучшится, а у меня быстрее по факту вот я вижу на 10-20 циклов ), причем обе реализации в одинаковых условиях
вот вам факты ) я считаю вы не правы
Опять вы перепрыгнули куда-то. Еще раз, как расшифровывается SIMD?
покажите перемножение матриц с транспонированием и я напишу что такое симд)
https://habr.com/ru/articles/884940/#comment_27957176
и в каком это мат аппарате покажите библиотеку примеры как этим пользоваться
раз такое дело
Скрытый текст
double time = __rdtsc();
// pref();
// fillPrefixSum1(a);
time = __rdtsc() - time;
printf("%12s: %.2f cycles\n", "", time*0.004096f);
у меня префиксы в два раза быстрее показывает 40-50 циклов, чем пример ниже, но в моей версии бывают броски по 200 циклов, кстати держу в курсе броски у вас и в матрицах будут ) но всё равно тогда можно сказать я решил задачку ) всё как пишут на стаковерфлоу ) ну или почти как в примере ниже можно сказать 1 в 1 )
for (int i = 1; i < 4096; i++)c[i] = c[i - 1] + a[i];
void transform(const int *src, int *dst, size_t N, int n)
Здесь не массивы, а указатели - разница всё таки есть )
А ссылки на массивы я тоже смотрю, только пониже в статье)
А если всё таки явно объявить массивы фиксированного размера и написать тот же цикл, обращаясь к ним по именам? С передачей массивов как параметров в C действительно не всё хорошо.
Ну да, в чистых сях массив как таковой, фактически, и нельзя передать как параметр. В плюсах можно толкнуть именно встроенный массив как таковой по ссылке. Компиляторы хорошо это понимают. У меня есть пример в статье, где GCC не векторизовал это на O2, но это, скорее, просто интересная особенность.
Можно массив завернуть в структуру для передачи. Суть та же самая останется.
Ну да, в чистых сях массив как таковой, фактически, и нельзя передать как параметр. В плюсах можно толкнуть именно встроенный массив как таковой по ссылке.
В сях остаётся передача массива по указателю. Индирекции это не добавляет, код генерируется тот же самый (godbolt), только обращаться придётся некрасиво: (*dst)[i]
.
Хорошее замечание, спасибо! Но там придётся либо зашивать размер в сигнатуру, либо передавать его параметром. Второй случай получается идентичным передаче по указателю.
Да, как-то смысла не видно. Вместо зашивания размера в сигнатуру практичнее тело функции в макрос завернуть.
Если передавать параметром, то оптимизаций не будет. Можно получить разве что сомнительную надежду на проверки границ компилятором за счёт VLA-в-списке-аргументов (N2778, принятый в C23).
А теперь поменяйте длину массива на что-то, не кратное размеру регистра, и посмотрите на кодген: появится обработка хвоста. Но именно знание длины массива позволило удалить этот хвост.
Ждем следущую разоблачающую статью про gather/scatter, когда в массив ходят через другой a[i] = b[c[i]]+const например
Мне кажется нельзя предъявлять функции void transform(const int* src, int* dst, size_t N, int n) - она ни в чем не виновата, так как ей передается переменная N которая определяет размер данных, в то время как transform для std::array размер уже определен на этапе компиляции - ARR_SIZE. Полагаю, что если в "transform для си" воткнуть ARR_SIZE для цикла, то GCC сможет догадаться о размере данных и сделать векторизацию.
А так статья классная!
Статья про то, что современный компилятор из нашей наивной реализации memcpy()
, с побайтным копированием, на -O3
оптимизации, сделает SIMD оптимизированную версию. Причём напишет версию очень похожую на libc с intrinsics, где скопирует начало до выровненного участка, дальше 512-битными числами, если AVX-512 есть, и так далее, и в конце хвостовую часть.
Каждый компилятор обычно предоставляет свой builtin __builtin_memcpy
, который при передачи ему constexpr длины, заранее знает, какими большими регистрами ему копировать, без рантайм ветвлений.
Фича std::array, по сравнению с указателем на память, в том, что есть constexpr значение размера, и компилятор знает как именно скопировать лучше.
солидарен, но у меня 1 и тоже приложение на С и С++ производительность разная, причем там где на С++ нормально, код не тривиальный пускай и удобный
Кстати, говоря о constexpr
- в качестве оффтопа вспомнил соревнование по простым числам на канале Dave's Garage, где один умелец сделал версию, работающую в компайл-тайме, которая, формально, вроде как победила)
https://github.com/PlummersSoftwareLLC/Primes/tree/drag-race/PrimeCPP/solution_3
так а std::array по итогу делает realloc ? вы же не рассмотрели jemalloc tсmalloc, в интернете пишут частое использование realloc приводит к фрагментации, тоесть само использование array ограничено, лучше чтобы пореже менялся размер получается ), а если делать пулл на array всё равно придётся очищать данные внутри арены приведет ли это к фрагментации арены?, или мы пользуемся указателями в массиве?
например у jemalloc есть векторизация, уже вопросы где быстрее если вообще так подумать
ноды с капасити тоже могут быть поидее же, а ноды это не реалок, а маллок
std::array - не делает никаких реаллоков. Это массив фиксированной, известной во время компиляции, длины. Вы путаете его с std::vector.
например у jemalloc есть векторизация,
Долго же вы, аж целый абзац, держались.
не реалок, а маллок
Фейспалм. Вы серъезно думаете, что маллок дешевле реаллока?
вставка будет по маллок у нод, а у array по realloc если он каноничен со всеми вытикающими, прочитайте название статьи и посмотрите фанкуч редукс на gcc
на каком месте g++? там разница между ними >1 секунды
там как раз array кстати
это не иногда
а, да, константа, тогда всё равно )) есть подвох, если придётся менять размер придётся копировать предыдущий в новый, и получается односвязный список удобнее массива, если массив не обьявлен явно на стеке ) тоесть с чего начиналось к тому и приходит, тогда получается, самое удобное это массив указателей на сущности 1 типа, тоесть на тривиальные типы данных <trivialObjs*> будет быстрее чем явно данные в array или vector, потомучто в этом случае итератор это будет адрес на обьект или последовательность )
тоесть в новом исполнении sequence<std::unique<trivialObjs*>>
why-can-a-t-be-passed-in-register-but-a-unique-ptrt-cannot
отсюда и новые move и лайфтайм на инстансе при вызове итератора
причем в С++ и С по разному передаётся, в С++ это будет (B &b) а в С (B *b)
Скрытый текст
//-std=c++26or23//gcc делает векторизацию кстати
template<typename T>
class Number:std::vector<T>
{
private:
T* n;//or T
public:
Number(){}
~Number(){}
};
int main()
{
std::array<std::unique_ptr<Number<int>*>,10> arr;
return 0;
}
Скрытый текст
#include <array>
#include <memory>
#include <vector>
#include <iostream>
template<typename T>
class Number:std::vector<T>
{
private:
T n;
public:
Number(){}
Number(T a){n=a;}
~Number(){}
T getN()
{
return n;
}
friend std::ostream& operator<<(std::ostream& os,Number<T> v)
{
return os << "{ n= " << v.n << " }";
}
T operator=(Number<T> rv)
{
return n=rv.n;
}
};
int main()
{
//std::array<std::unique_ptr<Number<int>*>,10> arr;
std::vector<std::unique_ptr<Number<int>*>> arr;
auto fillarr = [&](){
for(int i=0;i<10;i++){
std::unique_ptr<Number<int>*> v1 = std::make_unique<Number<int>*>(new Number<int>(i));
//arr.at(i)=(std::move(v1));
arr.push_back(std::move(v1));
}
};
fillarr();
auto print = [&](std::unique_ptr<Number<int>*> &v1){
Number<int> b=**v1.get();
std::cout<< b << '\n';
};
// std::cout<<**arr.begin()->get();
// std::cout<<**(arr.begin()+1)->get();
auto Bbegin = arr.begin();
auto Bend = (arr.end());
std::for_each(Bbegin,Bend,print);
return 0;
}
вот так еще можно)
https://godbolt.org/z/sK4q5xoeK array:10|~600/vector:10|~400
1000000 vector=array почти равны
https://godbolt.org/z/WeK47TcGG в 20 раз быстрее
int arr=malloc(sizeof(int)1000000); даже так в 20 раз быстрее
https://godbolt.org/z/6c8s3aqaP а так равны )
C++ -O3 -ffast-math -msse4.2 -mavx2 отстает на ~0.1-5
тоесть С быстрее на ~0.1-5
arr[i]=*it++; если уравнять ситуацию с С быстрее в 2 раза
https://godbolt.org/z/E4E7od9bo так еще быстрее
Я ни сколечки не лоулевел програмист, и на сигнатуре функции malloc моя экспертиза в нём заканчивается.
Но.
В худшем случае реаллок правда, если правильно понимаю, может быть дороже маллока, так как в первом может произойти мемкопи сверху обычной аллокации. Вот, например, как это имплеменировано в glibc.
А в остальном я, если честно, потерял нить беседы, поэтому на этом комментарии ограничусь))
Чел предлагает заменить реаллок на маллок в контексте добавления элементов в массив. Т.е., видомо, потом ручками перенести данные. Что в лучшем случае эквивалентно memcopy из realloc, в худшем - сильно медленнее. Только realloc еще и не факт что выделит новую область памяти, он может в каких-то случаях расширить существующую и ничего никуда не переносить.
Типо, аллокацию как в `std::vector`?
Да. Это то, что я смог вычленить из несвязного потока мыслей, из которого обычно и состоят комментарии этого автора.
Ну да, аллокация в масисвах - это немного неортодоксально)
я предлагаю маллок с односвязным списком, за место вектора , 1000000 операций с 512 симд у меня показало 0.5 а у вас?
с учетом что итератор кушает ресурсы интересно какая у вас скорость
Использовать односвязный список и аллоцировать каждую ноду через malloc?
так нода только против вектора, каждая нода добавляется же через маллок, а у вектора реаллок
а по теме массив против массива, у С++ броски до 10 раз в худшую сторону когда я смотрел, а С можно ускорить до 0.5 но это ансейф как я понимаю
поэтому интересно как вы ускоряете каждое обращение в итератор замедляет )
у меня вчера показало arr[i]=*it++; это стабильное замедление )
не знаю на сколько можно верить годболту но rdtsc можно точно увидеть в ассемблере
Все смешалось в кучу... Кони, люди, маллоки и односвязные списки.
проведите своё исследование вам никто не мешает, потом расскажете )
Перед тем как проводить свое исследование, мне бы хотя бы понять, что именно исследовать-то. Вы ни в одном комментарии четко и ясно ни разу не выразили ни одну мысль. На вопросы вы не отвечаете, а уже в следующем комментарии вы гарантированно перепрыгнете на что-то совершенно другое, так и не раскрыв мысль предыдущую.
Скрытый текст

а сколько это на С++ будет? выберайте array и ускоряйте )
и увидите то о чем реч + у трансформы итератор в С++!
фанкуч редукс посмотрите если не верите разница чуть больше секунды
Вот опять вы не в состоянии сформулировать мысль. Начать надо с того, что этот код должен делать. Скопировать из массива в него же самого?
На C++ это примерно в 30000 раз быстрее: 1.8e-05. Компилятор достаточно умен, чтобы выкинуть весь ненужный цикл нафиг. В итоге весь ваш СИМД замедлил программу в десятки тысяч раз, поздравляю.
Edit. с std::array все то же самое.
это не так про С++ вы напишите замерийте 1000000 цикл и покажите ответ это число от __rdtsc
сравнение std::array vs С-array
еще итератор замедляет если без векторизации сравнивать итератор давая доступ дает скорость в 2 раза меньше
Вы ссылку открывали? Там такой же цикл на 1000000 arr[i] = arr[i]
. Такое же rdtsc и вывод дает 1.8e-05. Если вы не знаете, эта запись означает 0.0000018.
это не продемонстрировано и не понятен (замер принципиален)
std::array (указатели и итераторы/simd) c-array(указатель/simd) нет сравнения, вы про ссылку пишите
скомпилируйте покажите
Что значит, не продемонстрированно?

std::array есть. Замер через Rdtsc, ровно как у вас. Результат в 30000 раз быстрее! Утритесь!
так нагляднее всё видно же вы согласны?
все теперь всё поняли
я прикрепляю ответ вам

чтото у вас не то на скрине и вообще сомнения присутствуют относительно замеров и убеждения что arr[i]=arr[i] это то что вам нужно, вам не кажется это очевидным?
Вот смотрите: godbolt. Там 6 функций, которые копируют из одного массива в другой, как вы хотели (но так и не смогли написать). Первые 4 - все возможные С++ подходы. Посмотрите их ассемблерный код: компилятор первые две заменил на вызов memmove, а F3 и F4 не векотризовал в виде функций, но при вставке тела в место вызова - заменил это все на memcpy. F5 и F6 - ручная векторизация через интринсики, компилятор получил примерно такой же код для них, как и для F4. Но F5 нельзя инлайнить, а F6 можно. И посмотрите на то, где функции вызываются: Вместо векторизованного тела F6 компилятор вставил memcpy (как и вместо F1-F4), а F5 осталась ручной векторизацией.
Посмотрите на вывод - все 6 функций работают одинаково быстро.
Отсюда вывод: ручная векторизация не нужна, потому что она работает не быстрее memcpy/memmove, которые компилятор сам отлично вставляет вместо почти любого вашего кода. А они уже вылизанны и векторизованы по максимуму.
Еще один вывод: векторизация действительно ускоряет однотипные операции, люди и без вас это отлично знают, и гораздо более умные дядьки уже все что надо векторизовали в библиотеках и научили компиляторы векторизовывать почти все что возможно. Если у вас тормозит программа, надо ее отпрофилировать и посмотреть ассемблерный код и, если вы обнаружите не векторизованное место, надо или чуть-чуть переписать программу (например, поменяв местами два цикла), или расставить компилятору пару подсказок (вроде #pragma ivdep или restrict).
вы скомпилируйте и прикрипите, поздравляю если у вас всё получилось
Вы по ссылке прошли? Этот сайт компилирует и запускает. И ассемблер показывает:
Скрытый текст

поздравляю вас, да прошел, спасибо
мне делать на С с массивами?
https://godbolt.org/z/GrK9161qM
Скрытый текст

включите флаги в вашей версии интересно сколько будет
https://godbolt.org/z/er91hTj3a я предлагаю тогда так сделать тогда быстрее(ну или так же) и сейфовее(на сколько хватает моего опыта) чем в Си, только я не проверил заполнилось ли
Во-первых, не стоит постоянно сильно редактировать ваши комментарии. Я не вижу, что вы там понаписали потом. Или это такая хитрость, написать какую-то бессмысленную реплику, не требующую ответа, а потом вставить свои "аргументы" и типа выиграть спор?
Во-вторых, отвечайте на мои комментарии, а не на свои. Опять же, мне не приходят уведомления.
В-третьих, посмотрите на ассемблерный код вашего решения и то, что я привел выше. И ваша векторизация и моя скомпилировались в ту же самую конструкцию:
// У меня
vmovaps ymm0, YMMWORD PTR [rdi+rax]
vmovaps YMMWORD PTR [rsi+rax], ymm0
add rax, 32
cmp rax, 4000000
// У вас
vmovaps ymm0, YMMWORD PTR arr1[rax]
add rax, 32
vmovaps YMMWORD PTR arr[rax-32], ymm0
cmp rax, 4000000
Те же самые инструкции: две vmovamps YMMWORD, прибавление 32 и сравнение с 400000.
Работает это ровно столько же. Можете сами запустить: godbolt
Все ваши пляски с итераторами, совершенно глупое использование функции mm256_set_ps, предназначенной для загрузки разряженных данных и принимающей 8 чертовых аргументов, вместо mm256_load_ps всего с одним - вообще бессмысленны.
сейфовее(на сколько хватает моего опыта)
Ничего не сейфовее. Самое сейфовое и понятное - std::copy. Ну, или b[i] = a[i]. И все компилирутется в memcpy. А писать руками интринсики - только плодить места для ошибок. Особенно в вашем исполнении с 8-ю аргументами.
да не в этом проблема, в гцц вы хотябы ускорить можете, и гцц входит на какойто стадии в ллвм, вы лучше пошире посмотрите на вопрос, например оба примера и С и С++ на clang не скомпилируются(причем на локалке компилируются), и чтото мне подсказывает другие вещи ллвм на другом языке даст провернуть, понимаете куда ветер дует?
я вообще не спорю просто показал, а тоесть лоад разобьёт регистр вы так хотите?
сет удобнее економит кучу времени и строк кода
ок оба примеры одинаковы, но скорость разная
С++ сефовее С, деструкторы и умные указатели и итераторы и куча классных штук, которые я в нём люблю, я даже от своего примера снова полюбил С++
я готов дать накладные расходы на итератор, потомучто им просто удобнее пользоваться лично мне например, вы на С попишите там всё реализовывать вообще с нуля надо если исходить из идеологии, велосипеда
вот нюанс изза которого не компилировал кланг это я конечно, не прав но суть https://godbolt.org/z/hzhhPa3ee
https://godbolt.org/z/3Pvnorxr8 так еще быстрее фактически 0.01 )
вставьте проверку перед циклом if((&arr)==принятому аргументу то верните тот же массив, но суть же не в этом правильно? вы же хотите замерить тонкие нюансы поэтому вы отправьте принципиально другой адрес или возьмите итератор от array или еще как-то если язык позволяет )
Ненавижу, _____, C++ массивы: https://habr.com/ru/companies/pvs-studio/articles/822911/
std::array в С++ быстрее массива в С. Иногда