Почему иногда фреймворк Apple Accelerate тормозит?


Я играю с кодом C и Swift 3.0, используя vecLib и Accelerate framework от Apple как динамическую библиотеку + мой код в проекте на основе C lang и Swift.

А в ситуации с вызовом обёртки Apple из framework SIMD-инструкции с 1 или ‹ 4 элементами вычислительная функция типа vvcospif() из framework работает медленнее, чем простой стандартный cos(x * PI), когда функции вызываются из цикла рядом 1.000 раз в качестве примера.

Я знаю о разнице между vvcospif() и cos(), я должен использовать именно vvcospif() вместо x * PI.

Пример на игровой площадке, вы можете просто скопировать код и запустить его:

import Cocoa
import Accelerate

func cosine_interpolate(alpha: Float, a: Float, b: Float) -> Float {
    let ft: Float = alpha * 3.1415927;
    let f: Float = (1 - cos(ft)) * 0.5;

    return a + f*(b - a);
}

var start: Date = NSDate() as Date

var interp: Float;

for index in 0..<1000 {
   interp = cosine_interpolate(alpha: 0.25, a: 1.0, b: 0.75)
}

var end = NSDate();
var timeInterval: Double = end.timeIntervalSince(start);

print("cosine_interpolate in \(timeInterval) seconds")

func fast_cosine_interpolate(alpha: Float, a: Float, b: Float) -> Float {
    var x: Float = alpha
    var count: Int32 = 1

    var result: Float = 0
    vvcospif(&result, &x, &count)

    let SINSIN_HALF_X: Float = (1 - result) * 0.5;

    return a + SINSIN_HALF_X * (b - a);
}

start = NSDate() as Date

for index in 0..<1000 {
    interp = fast_cosine_interpolate(alpha: 0.25, a: 1.0, b: 0.75)
}

end = NSDate();
timeInterval = end.timeIntervalSince(start);

print("fast_cosine_interpolate in \(timeInterval) seconds")

Мой вопрос:

Почему vvcospif() работает медленно в этом примере?

Может быть, потому, что vvcospif() это оболочка для среды выполнения Objective-C, а преобразование структур данных/копирование памяти из Intel SIMD -> Objective-C -> среда выполнения Swift медленнее, чем крошечная cos()?

У меня также есть проблема с производительностью с кодом C +

#include <Accelerate/Accelerate.h>

vvcospif(resultVector, inputVector, &count);

когда inputVector и resultVector представляют собой небольшие массивы с 1 или 2 элементами или просто переменной с плавающей запятой и вызывают в цикле ~ 1 000 000 раз.

cos(x * PI) время расчета около 20 мс.

а также

vvcospif(x) с обработкой одного float или float array[2] - время расчета около 80 мс! Где Акселерация? :)

Да, в Xcode я использую оптимизацию компилятора -O -whole-module-optimization с опцией всего модуля. включено.


person menangen    schedule 08.10.2016    source источник
comment
Я предполагаю, что a + f(b - a) в первом методе должно быть a + f*(b - a)? – Я запустил ваш код на MacBook и получил следующие тайминги: cosine_interpolate: 0,74 миллисекунды, fast_cosine_interpolate: 0,1 миллисекунды.   -  person Martin R    schedule 08.10.2016
comment
Спасибо, это была ошибка в коде с f*action. На моем mac mini: cosine_interpolate за 0,461017966270447 секунд fast_cosine_interpolate за 0,545050024986267 секунд   -  person menangen    schedule 08.10.2016
comment
-o устанавливает имя выходного файла. -O3 -ffast-math включает полную оптимизацию. Объясняет ли это, что ваше время на 3 порядка медленнее, чем у Мартина?   -  person Peter Cordes    schedule 08.10.2016
comment
Нет, я обновил вопрос и исправил ошибку с помощью -O: в Xcode была выбрана оптимизация по умолчанию для выпуска и отладки, также эти результаты были в Swift 3.0 и Xcode 8 по умолчанию, игровая площадка Sierra. В проекте игровой площадки я не могу выбрать флаги оптимизации...   -  person menangen    schedule 08.10.2016
comment
vvcospif — это функция vForce. vForce работает с векторами произвольной длины, что создает некоторые накладные расходы, поэтому не рекомендуется использовать его для очень коротких векторов. Сама Apple рекомендует: рассмотрите возможность использования vForce, когда более 16 элементов. Для скалярного использования рассмотрите возможность использования __cospi{f}, доступного в iOS 7 и OS X 10.9 согласно уже цитированному источнику.   -  person njuffa    schedule 10.10.2016
comment
Пожалуйста, посмотрите эти недавние твиты twitter.com/kelvindotchan/status/1086417363031908352?s=21 twitter.com/kelvindotchan/status/1086423804367200256?s=21 я выполнил ровно один вызов vForce для вычисления abs (большой большой вектор), и я удивлен, что библиотека Джереми, которая в основном выполняет цикл с использованием указателей вместо быстрого доступа к массиву, фактически немного превзошла Accelerate. Хотя это специальный тест, я действительно подозреваю, что Accelerate не так быстр, как должен быть. Какие-нибудь советы? Обратите внимание, что я также не делал никаких специальных трюков с флагами компилятора для его библиотеки.   -  person kawingkelvin    schedule 20.01.2019


Ответы (1)


Более подробное обсуждение с примерами см. в "Введение в Fast Bezier (и опробование Accelerate.framework) ".

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

Суть SIMD-функций в том, что вы настраиваете все свои данные в нужном формате, а затем вызываете их только один раз. Таким образом, стоимость вызова функции компенсируется оптимизированным SIMD-кодом, который вы вызываете.

Но помните, вам не нужно вызывать Accelerate, чтобы получить SIMD-оптимизацию. Компилятор вполне способен заметить, что вы написали цикл, и сам превратить его во встроенный SIMD-алгоритм (и он делает это все время). Так что во многих простых задачах компилятор в любом случае выиграет. Подумайте об этом: если бы вызов vvcospif со счетчиком 1 был быстрее, чем вызов cos, разве они не реализовали бы cos таким образом?

Я мало играл с вашим кодом, но если вы хотите улучшить его производительность с помощью Accelerate, вам нужно подумать о том, как упорядочить все ваши входные данные, чтобы вы могли вызывать vvcospif один раз с большим N. Это вполне возможно в этом случае это будет намного быстрее, чем цикл (поскольку cos не тривиален).

Если вам нужен пример Accelerate на практике и то, как вам нужно организовать свои данные, см. PinchText. Этот код вычисляет смещения для страницы, содержащей несколько тысяч глифов, на основе до 10 касаний в режиме реального времени с анимацией (см. PinchText.mov, чтобы узнать, как выглядит результат). В частности, посмотрите на adjustViewPositions:count:forTouchPoint:. Обратите внимание, что count большое, и данные преобразовываются шаг за шагом без циклов. Даже добавление (очень дорогого) вызова метода ObjC в этот метод не имеет большого значения, потому что это делается только один раз. Избавление от вызовов функций в циклах — важная часть производительного программирования.

person Rob Napier    schedule 08.10.2016