Java может распознавать SIMD-преимущества ЦП; или есть просто оптимизационный эффект разворачивания цикла

Эта часть кода взята из метода dotproduct моего векторного класса. Метод выполняет вычисление внутреннего продукта для целевого массива векторов (1000 векторов).

Когда длина вектора является нечетным числом (262145), время вычисления составляет 4,37 секунды. Когда длина вектора (N) равна 262144 (кратно 8), время вычисления составляет 1,93 секунды.

     time1=System.nanotime();
     int count=0;
     for(int j=0;j<1000;i++)
     {

             b=vektors[i]; // selects next vector(b) to multiply as inner product.
                           // each vector has an array of float elements.

             if(((N/2)*2)!=N)
             {
                 for(int i=0;i<N;i++)
                 {
                     t1+=elements[i]*b.elements[i];
                 }
             }
             else if(((N/8)*8)==N)
             {
                 float []vek=new float[8];
                 for(int i=0;i<(N/8);i++)
                 {
                     vek[0]=elements[i]*b.elements[i];
                     vek[1]=elements[i+1]*b.elements[i+1];
                     vek[2]=elements[i+2]*b.elements[i+2];
                     vek[3]=elements[i+3]*b.elements[i+3];
                     vek[4]=elements[i+4]*b.elements[i+4];
                     vek[5]=elements[i+5]*b.elements[i+5];
                     vek[6]=elements[i+6]*b.elements[i+6];
                     vek[7]=elements[i+7]*b.elements[i+7];


                     t1+=vek[0]+vek[1]+vek[2]+vek[3]+vek[4]+vek[5]+vek[6]+vek[7];
                     //t1 is total sum of all dot products.
                 }
             }
     }
     time2=System.nanotime();
     time3=(time2-time1)/1000000000.0; //seconds

Вопрос: может ли сокращение времени с 4,37 с до 1,93 с (в 2 раза быстрее) быть мудрым решением JIT использовать SIMD-инструкции или просто положительным эффектом моего развертывания цикла?

Если JIT не может автоматически оптимизировать SIMD, то в этом примере также нет оптимизации развертывания, выполняемой JIT автоматически, так ли это?

Для 1M итераций (векторов) и для размера вектора 64 множитель ускорения достигает 3,5X (преимущество кеша?).

Спасибо.


person huseyin tugrul buyukisik    schedule 03.07.2013    source источник


Ответы (2)


В вашем коде куча проблем. Вы уверены, что измеряете то, что думаете?

Ваш первый цикл делает это с более условным отступом:

 for(int j=0;j<1000;i++) {
     b=vektors[i]; // selects next vector(b) to multiply as inner product.
                   // each vector has an array of float elements.
 }

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

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

Я не видел, чтобы JVM генерировала векторизованный код для чего-то вроде вашего второго цикла, но я, возможно, на несколько лет устарел в том, что делают JVM. Попробуйте использовать -XX:+PrintAssembly, когда вы запускаете свой код и проверяете код, который генерирует opto.

person tmyklebu    schedule 03.07.2013
comment
Извините, я собирался удалить это }, но забыл удалить. (отладка) - person huseyin tugrul buyukisik; 04.07.2013
comment
Фу. Да, это немного раздражает, чтобы заставить его работать. Вам нужно, чтобы JVM нашла что-то вроде hsdis-amd64.so. Вам нужно пройти -XX:+UnlockDiagnosticVMOptions до -XX:+PrintAssembly. И вы должны капитализировать все правильно. - person tmyklebu; 04.07.2013
comment
Не удалось загрузить hsdis-amd64.dll; библиотека не загружается; PrintAssembly отключен ----> поиск места загрузки, тогда я покажу это как внешний файл jar (или библиотеку)? - person huseyin tugrul buyukisik; 04.07.2013
comment
Создайте свой собственный openjdk. Или создайте пакет hsdis-base или что-то еще. Во всяком случае, когда я пробовал это с подобным кодом, я все еще вижу, что он выдает кучу скалярных инструкций. Итог: не используйте java там, где вообще важна скорость. - person tmyklebu; 04.07.2013
comment
Я использовал opencl, но читал, что прямое использование avx на данный момент может ускориться как минимум в 10 раз. - person huseyin tugrul buyukisik; 04.07.2013

Я провел небольшое исследование по этому поводу (и опираюсь на знания из аналогичного проекта, который я сделал на C с умножением матриц), но отнеситесь к моему ответу с долей скептицизма, поскольку я ни в коем случае не являюсь экспертом по этой теме.

Что касается вашего первого вопроса, я думаю, что ускорение связано с развертыванием вашего цикла; вы делаете примерно на 87% меньше проверок условий с точки зрения цикла for. Насколько мне известно, JVM поддерживает SSE начиная с версии 1.4, но чтобы на самом деле контролировать, использует ли ваш код векторизацию (и знать наверняка), вам нужно использовать JNI.

См. пример JNI здесь: Генерируют ли какие-либо JIT-компиляторы JVM код, использующий векторизованные инструкции с плавающей запятой?

Когда вы уменьшаете размер вашего вектора до 64 с 262144, кеш, безусловно, играет роль. Когда я делал этот проект на C, нам пришлось реализовать блокировку кеша для матриц большего размера, чтобы воспользоваться преимуществами кеша. Одна вещь, которую вы, возможно, захотите сделать, это проверить размер вашего кеша.

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

person Arjun Baokar    schedule 03.07.2013
comment
Также время вычисления вектора (размер = 13) равно вектору (размер = 13 * 16), разве это не странно? Простое умножение размера задачи на 16 дает одинаковое время (почти 1 секунду для обоих). Я думал, что процессор силен в ветвлении. - person huseyin tugrul buyukisik; 04.07.2013
comment
13 крошечный, а 13*16 позволяет вам использовать разворачивание цикла. Но оба вектора довольно маленькие, поэтому я сомневаюсь, что вы увидите большие временные вариации. Накладные расходы программы, вероятно, затмевают оба их времени расчета. - person Arjun Baokar; 04.07.2013
comment
Если бы его второй цикл не был нарушен, два цикла использовали бы кеш точно так же. Ускорение не из-за сокращенной проверки условий. Это потому, что вычисления организованы по-разному. Если вы уменьшите размер вектора до 64, то все уместится в L1, и совершенно не имеет значения, в каком порядке вы его проходите. арифметика обычно не является узким местом. - person tmyklebu; 04.07.2013
comment
Я просто удаляю } в верхней части. Я забыл удалить, когда я отлаживал. Извини. Итак, вы имеете в виду, что разный размер вектора означает разный уровень кеша. SIMD вообще нет? - person huseyin tugrul buyukisik; 04.07.2013
comment
Чтобы использовать JNI, мне нужен этот файл .h, чтобы действительно использовать ЦП? Пробовал и не получилось(скачал jar javacpp и показал путь) но буду стараться. - person huseyin tugrul buyukisik; 04.07.2013