Что такое более быстрый glUniform4f / glUniform4fv с учетом всех видов оптимизации?

вот подписи.

glUniform4f(GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
glUniform4fv(GLint location, GLsizei count, const GLfloat *v);

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

3 ответов


С *v вариант в основном существует для установки униформы, которые имеют тип массива, спецификация OpenGL явно позволяет использовать варианты массива также для установки скалярных значений, используя количество единиц.

позвольте мне процитировать спецификацию OpenGL (подчеркивает добавил себя):

команды glUniform{1/2/3/4}{f / i}v можно использовать для изменения одна равномерная переменная или однородный массив переменных. Эти команды проходят счетчик и указатель на значения загружается в однородную переменную или массив однородных переменных. счет 1 следует использовать при изменении значения одна равномерная переменная, и количество 1 или больше может использоваться для изменения всего массива или его части.

это OpenGL 2.1 Spec, однако он гласит то же самое для OpenGL 4.2 Spec.

на самом деле наоборот разрешены. Предположим, у вас есть униформа типа vec3 v[2] и вы запрашиваете его местоположение с помощью glGetUniformLocation(), вернуть 6. Это значит, что 6 на самом деле расположении v[0].


теперь вернемся к первоначальному вопросу: какой вариант быстрее?

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

Е. Г. рассмотрим следующий код:

void glUniform1f(GLint  location,  GLfloat  v0)
{
    glUniform1fv(location, 1, &v0);
}

в этом случае вариант выбора будет быстрее. Однако возможен и следующий вариант:

void glUniform1fv(GLint  location, GLsizei count, GLfloat * value)
{
    int i;

    for (i = 0; i < count; i++) {
        glUniform1f(location, *value);
        value++;
        location++;
    }
}

в этом случае вариант без массива будет быстрее.

лично я бы сказал (и это исключительно мое личное мнение), что ранние реализации OpenGL, возможно, реализовали варианты массива с использованием вариантов без массива, так как это более простая реализация, без каких-либо других изменений в остальной части библиотеки OpenGL. С другой стороны, это также гораздо более медленная реализация, поскольку она включает в себя цикл, который, скорее всего, не нужен с современными графическими адаптерами, и, следовательно, современные реализации, скорее всего, реализуют варианты без массива поверх вариантов массива.

варианты выбора имеют и другие преимущества. Рассмотрим следующая функция:

struct v3 {
     GLfloat x;
     GLfloat y;
     GLfloat z;
};

void setUniform (GLint location, struct v3 * vPtr) {
    glUniform3f(location, vPtr->x, vPtr->y, vPtr->z);
}

разыменование vPtr три раза просто для вызова функции без массива довольно глупо и вряд ли когда-либо быстрее, чем следующая реализация:

void setUniform (GLint location, struct v3 * vPtr) {
    glUniform3fv(location, 1, (const GLfloat *)vPtr);
}

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


Они не сопоставимы. Первый устанавливает равномерную переменную, которая является скалярной (под которой я подразумеваю один 4-вектор), последний устанавливает тот, который является массивом (из 4-векторов).

возможно, массив длины 1 можно рассматривать как скаляр, и наоборот, но это было бы извращением. Таким образом, вы никогда не оказываетесь в ситуации, когда у вас есть выбор между ними.

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


спросите себя, почему версия *v существует в первую очередь. Чтобы понять ответ, вам нужно знать, как процессоры разговаривают с графическими процессорами.

современные компьютеры общаются с GPU через Динамический Доступ К Памяти. Это еще одна часть оборудования, которая массово перемещает память с одного устройства на другое. Вы говорите, переместите все из пустоты * a в пустоту * b, и он уходит и делает это (без процессора).
Однако, когда вы вызываете функцию open-gl, что она на самом деле делает это запись команды / данных в специальный предварительно выделенный блок памяти, называемый команда. В какой-то момент позже он сообщает DMA-контроллеру переместить это. Это работает так, потому что...
a) вы можете изменить содержимое памяти до окончания DMA-передачи. Копирование памяти останавливает любые потенциальные условия гонки.
б)
виртуальный означает, что то, что выглядит как блок памяти в компьютерной программе, может быть рассеянной (в страницы) в реальной физической памяти. Списки команд гарантированно имеют свойство, что они являются блоком в физической памяти.

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

Так... glUniform * v существует, потому что не-векторная версия так же низка, как 50% эффективна на большинстве реализаций. Это не удивительно. Если API предоставляет функцию, это обычно потому, что это либо невозможно, либо непомерно дорого достичь того же самого любым другим способом.