AVX: выравнивание данных: сбой хранилища, сохранение, загрузка, загрузка не

Я модифицирую нейронную сеть RNNLM для изучения языковой модели. Однако, учитывая размер моего корпуса, он работает очень медленно. Я попытался оптимизировать подпрограмму matrix*vector (на которую приходится 63% общего времени для небольшого набора данных (я ожидаю, что на больших наборах будет хуже)). Сейчас я застрял с внутренностями.

    for (b=0; b<(to-from)/8; b++) 
    {
        val = _mm256_setzero_ps();
        for (a=from2; a<to2; a++) 
        {
            t1 = _mm256_set1_ps (srcvec.ac[a]);
            t2 = _mm256_load_ps(&(srcmatrix[a+(b*8+from+0)*matrix_width].weight));
            //val =_mm256_fmadd_ps (t1, t2, t3)
            t3 = _mm256_mul_ps(t1,t2);
            val = _mm256_add_ps (val, t3);
        }
        t4 = _mm256_load_ps(&(dest.ac[b*8+from+0]));
        t4 = _mm256_add_ps(t4,val);
        _mm256_store_ps (&(dest.ac[b*8+from+0]), t4);
    }

Этот пример аварийно завершает работу:

_mm256_store_ps (&(dest.ac[b*8+from+0]), t4);

Однако, если я перейду на

_mm256_storeu_ps (&(dest.ac[b*8+from+0]), t4);

(с u для невыровненного, я полагаю) все работает как задумано. Мой вопрос: почему load работает (в то время как это не должно, если данные не выровнены), а store - нет. (к тому же оба работают на одном и том же адресе).

dest.ac были выделены с использованием

void *_aligned_calloc(size_t nelem, size_t elsize, size_t alignment=64)
{
    size_t max_size = (size_t)-1;

    // Watch out for overflow
    if(elsize == 0 || nelem >= max_size/elsize)
        return NULL;

    size_t size = nelem * elsize;
    void *memory = _mm_malloc(size+64, alignment);
    if(memory != NULL)
        memset(memory, 0, size);
    return memory;
}

и это не менее 50 элементов. (Кстати, с VS2012 у меня есть недопустимая инструкция для какого-то случайного назначения, поэтому я использую Linux.)

заранее спасибо, Аркантус.


person Arkantus    schedule 19.05.2015    source источник
comment
каково значение from? Есть ли шанс, что встроенная функция _mm256_load_ps на самом деле реализована как 2 128-битные загрузки?   -  person Come Raczy    schedule 19.05.2015
comment
Значение from при сбое равно 891. &(dest.ac[b*8+from+0]) = 0x957e6c . Итак, есть доступ в середине таблицы, и это не выровнено.   -  person Arkantus    schedule 20.05.2015
comment
с таким значением еще более удивительно, что нагрузка работает. Вы проверили, действительно ли вы загружаете правильные значения (для этого значения from)?   -  person Come Raczy    schedule 20.05.2015
comment
Вы должны проверить сгенерированный ASM и посмотреть, пересчитывает ли он индекс массива каждый раз во внутреннем цикле. Если это так, вытащите постоянную часть из цикла. Что обычно хорошо работает, так это увеличение внешнего цикла b на 8 * matrix_width вместо умножения b * 8 в индексном выражении. gcc кажется плохим в преобразовании циклов, чтобы поддерживать только масштабированную версию счетчика циклов, когда вы не записываете цикл таким образом.   -  person Peter Cordes    schedule 24.06.2015
comment
Кроме того, встроенные функции set1 могут работать медленно. Будьте осторожны с ними. Надеюсь, это компилируется в файл vbroadcastss ymm, [mem]. Если вы можете организовать свои структуры данных так, чтобы они не нуждались во внутреннем цикле, это может быть быстрее. Простой обмен внутренними/внешними циклами, чтобы одно и то же srcvec использовалось для всех значений b, было бы медленнее из-за необходимости собирать несмежные данные из srcmatrix. vbroadcastss составляет 2 мопса и 5 циклов задержки из памяти (на Haswell). На 1 цикл меньше при 128-битном назначении вместо 256. Пропускная способность составляет 1 на цикл (может работать только на порту 5 на SnB/IvB/HSW).   -  person Peter Cordes    schedule 24.06.2015


Ответы (1)


TL:DR: в оптимизированном коде загрузки будут складываться в операнды памяти для других операций, которые не имеют требований к выравниванию в AVX. Магазины не будут.


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

Я провел небольшой эксперимент с gcc 4.9, и он вообще не генерирует vmovaps для _mm256_load_ps, так как я использовал результат загрузки только в качестве входных данных для одной другой инструкции. Он генерирует эту инструкцию с операндом в памяти. Инструкции AVX не требуют выравнивания своих операндов в памяти. (Существует снижение производительности при пересечении строки кэша и еще большее снижение при пересечении границы страницы, но ваш код все еще работает.)

С другой стороны, хранилище генерирует инструкцию vmov.... Поскольку вы использовали версию, требующую выравнивания, она дает сбой на невыровненных адресах. Просто используйте невыровненную версию; это будет так же быстро, когда адрес выровнен, и все равно будет работать, когда это не так.

Я не проверил ваш код тщательно, чтобы убедиться, что все доступы ДОЛЖНЫ быть выровнены. Я предполагаю, что нет, судя по тому, как вы это сформулировали, чтобы просто спросить, почему вы также не получаете ошибки для невыровненных нагрузок. Как я уже сказал, возможно, ваш код просто не скомпилировался в какие-либо инструкции загрузки vmovaps, или даже «выровненные» загрузки AVX не приводят к ошибкам на невыровненных адресах.

Вы используете AVX (без AVX2 или FMA?) на процессоре Sandy/Ivybridge? Я предполагаю, что именно поэтому ваши инструкции FMA закомментированы.

person Peter Cordes    schedule 08.06.2015
comment
Да, я использую AVX на процессоре Sandy. и да, некоторые доступы не выровнены! Спасибо ! Я буду использовать версию u теперь, когда я понимаю, почему! - person Arkantus; 24.06.2015