Search
Write a publication
Pull to refresh

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/

Скрытый текст
скрин отсюда prefix/
скрин отсюда 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 с исходным кодом и я принесу публичные извинения.

Ну блин, вы почитайте ваш ассемблерный выход-то. Там нет никакой векторизации вычислений, ровно как я и говорил. Посчитайте, сколько у вас там операций 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?

раз такое дело

Скрытый текст
    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];

simd-prefix-sum-on-intel-cpu

void transform(const int *src, int *dst, size_t N, int n)

Здесь не массивы, а указатели - разница всё таки есть )

А если всё таки явно объявить массивы фиксированного размера и написать тот же цикл, обращаясь к ним по именам? С передачей массивов как параметров в C действительно не всё хорошо.

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

Можно массив завернуть в структуру для передачи. Суть та же самая останется.

Тогда массив будет копироваться при передаче, это дорого.

Ну да, в чистых сях массив как таковой, фактически, и нельзя передать как параметр. В плюсах можно толкнуть именно встроенный массив как таковой по ссылке.

В сях остаётся передача массива по указателю. Индирекции это не добавляет, код генерируется тот же самый (godbolt), только обращаться придётся некрасиво: (*dst)[i].

Хорошее замечание, спасибо! Но там придётся либо зашивать размер в сигнатуру, либо передавать его параметром. Второй случай получается идентичным передаче по указателю.

Да, как-то смысла не видно. Вместо зашивания размера в сигнатуру практичнее тело функции в макрос завернуть.

Если передавать параметром, то оптимизаций не будет. Можно получить разве что сомнительную надежду на проверки границ компилятором за счёт VLA-в-списке-аргументов (N2778, принятый в C23).

Побоюсь про VLA что-нибудь говорить)

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

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

Ждем следущую разоблачающую статью про 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 сможет догадаться о размере данных и сделать векторизацию.

А так статья классная!

Спасибо!

Но тогда нельзя будет кастомизировать размер массива. Ну и в целом, на O3 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;
}

вот так еще можно)

Я ни сколечки не лоулевел програмист, и на сигнатуре функции 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/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 не скомпилируются(причем на локалке компилируются), и чтото мне подсказывает другие вещи ллвм на другом языке даст провернуть, понимаете куда ветер дует?

я вообще не спорю просто показал, а тоесть лоад разобьёт регистр вы так хотите?

сет удобнее економит кучу времени и строк кода

ок оба примеры одинаковы, но скорость разная

С++ сефовее С, деструкторы и умные указатели и итераторы и куча классных штук, которые я в нём люблю, я даже от своего примера снова полюбил С++

я готов дать накладные расходы на итератор, потомучто им просто удобнее пользоваться лично мне например, вы на С попишите там всё реализовывать вообще с нуля надо если исходить из идеологии, велосипеда

Офигеть! Если копировать куски массива параллельно в несколько потоков, оно работает быстрее! Обязательно напишите, когда окроете, что вода мокрая, а огонь - горячий.

вставьте проверку перед циклом if((&arr)==принятому аргументу то верните тот же массив, но суть же не в этом правильно? вы же хотите замерить тонкие нюансы поэтому вы отправьте принципиально другой адрес или возьмите итератор от array или еще как-то если язык позволяет )

Я написал ровно как вы хотели. То же самое но на с++. Если вы хотите померять что-то еще, то вы и напишите это самое что-то еще.

Sign up to leave a comment.