Я следил за инструктором Intel для SIMD на Java с Панама. Я хочу проделать несколько простых операций с массивами:
Вот скалярный и векторный цикл с сайта:
public static void scalarComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * - 1.0f;
}
}
public static void vectorComputation(float[] a, float[] b, float[] c) {
int i = 0;
for (; i < (a.length & ~(species.length() - 1));
i += species.length()) {
FloatVector<Shapes.S256Bit> va = speciesFloat.fromArray(a, i);
FloatVector<Shapes.S256Bit> vb = speciesFloat.fromArray(b, i);
FloatVector<Shapes.S256Bit> vc = va.mul(va).
add(vb.mul(vb)).
neg();
vc.intoArray(c, i);
}
for (; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
Когда я измеряю время:
float [] A = new float[N];
float [] B = new float[N];
float [] C = new float[N];
for(int i = 0; i < C.length; i++)
{
C[i] = 2.0f;
A[i] = 2.0f;
B[i] = 2.0f;
}
long start = System.nanoTime();
for(int i = 0; i < 200; i++)
{
//scalarComputation(C,A,B);
//vectorComputation(C,A,B);
}
long end = System.nanoTime();
System.out.println(end - start);
У меня всегда больше времени для вектора, чем для скаляра. Вы знаете почему? Спасибо.
Для небольших (достаточно) векторов накладные расходы на передачу информации механизму векторной обработки превысят экономию,
N равно 345600 = 480 * 720
@StephenC: Я предполагаю, что OP находится на x86-64, где все текущие микроархитектуры тесно интегрируют векторные ALU очень. Стоит использовать SIMD для копирования 16 байт, если источник еще не был в целочисленных регистрах! Скалярная математика FP в любом случае использует те же регистры XMM0..15, что и векторная математика, например addss
(скалярная одинарная точность) вместо addps
(в упаковке одинарной точности). Для 256-битных векторов потребуются регистры YMM (если Панама не эмулирует широкие векторы поверх 128-битных SIMD ...). XMM0..15 - нижние половинки YMM0..15
Если вы повторяете это только 200 раз, вы можете увидеть 256-битные эффекты запуска AVX. Если это работает с ~ 8 числами с плавающей запятой за такт (1 vmulps + 1 vfmsubps), ваш интервал тестирования может быть всего 2 мс на Haswell или Skylake с тактовой частотой 4 ГГц. Это все еще 8 миллионов тактовых циклов, так что вы, вероятно, в порядке с включением верхних половин исполнительных блоков. Агнер Фог заметил, что период разминки на Skylake составляет 14 часов: agner.org/optimize/blog/read.php?i=415#415. (И, кстати, 8 чисел с плавающей запятой mul + FMA за такт на самом деле здесь невозможно из-за узкого места во внешнем интерфейсе из инструкций load + store.)
@ K.Vu: вы разогрели JVM, чтобы было время для JIT-компиляции горячего цикла? И вы проверяли, что скалярная версия не оптимизирует работу в цикле? (Ничего не читает результат). Хорошая проверка состоит в том, что время линейно зависит от количества повторов. Если он масштабируется, но не линейно, накладные расходы на запуск являются проблемой. Если он не масштабируется, ваш тест будет оптимизирован. Если он линейный, то, возможно, вы измеряете то, что планировали (но все же нет гарантии).
На каком оборудовании вы тестируете? Если вы включите JIT-скомпилированный asm для внутреннего цикла в свой вопрос (Как увидеть JIT-скомпилированный код в JVM?) и отметите [x86]
, я могу сказать вам, почему версия в Панаме медленнее (если это не была ошибка измерения), если вы используете x86 (agner.org/optimize). например возможно, JIT-компилятор автоматически векторизует ваш простой скалярный цикл, используя лучший выбор инструкций, если ваш панамский код заставляет фактическое умножение на -1.0
вместо SUB или FMSUB вместо ADD или FMADD. Или неправильно выровненные массивы на Sandybridge?
У меня процессор Intel (R) Core (TM) i7-7500U @ 2,70 ГГц. Кэш L1 составляет 128 КБ, L2 - 512 КБ, а L3 - 4096 КБ. Я только что установил -XX: TypeProfileLevel = 121 -XX: + UseVectorApiIntrinsics в моей конфигурации запуска.
Я уже сделал проект HPC на C с SSE и SSE2, и мне было любопытно, может ли Java сделать то же самое.
Вам действительно нужно написать правильный тест. Java требуется некоторое время (секунды), прежде чем она будет работать должным образом быстро, а результаты теста покажутся всего лишь миллисекундами. Забудьте об этом, используйте JMH, все остальное тратит время.
Вы используете неправильную ветку: создайте из вектор. Вам также необходимо использовать JMH для получения правильных измерений - здесь - это некоторые сторонние тесты, написанные для Vector API.
Чтобы узнать о различиях, которые API векторов вносит в расчет скалярного произведения, см. здесь.
Как вы пришли к выводу, что OP использует неправильную ветку - я не вижу этого, упомянутого в их сообщении?
Какое значение имеет
N
в ваших тестах?