Использование ограничительного ограничителя с массивами переменной длины C99 (VLA)
Я исследую, как различные реализации простых циклов в C99 автоматически векторизуются на основе сигнатуры функции.
Вот мой код:
/* #define PRAGMA_SIMD _Pragma("simd") */
#define PRAGMA_SIMD
#ifdef __INTEL_COMPILER
#define ASSUME_ALIGNED(a) __assume_aligned(a,64)
#else
#define ASSUME_ALIGNED(a)
#endif
#ifndef ARRAY_RESTRICT
#define ARRAY_RESTRICT
#endif
void foo1(double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo2(double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Undetermined size version */
void foo3(int n, double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo4(int n, double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Static array versions */
void foo5(double ARRAY_RESTRICT a[2048], const double ARRAY_RESTRICT b[2048], const double ARRAY_RESTRICT c[2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo6(double ARRAY_RESTRICT a[2048], const double ARRAY_RESTRICT b[2048], const double ARRAY_RESTRICT c[2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* VLA versions */
void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo8(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
Когда я собираю с
$ icc -O3 -std=c99 -opt-report5 -mavx -S foo.c
icc: remark #10397: optimization reports are generated in *.optrpt files in the output location
Я вижу, что случаи VLA не автоматически векторизованы, но когда я добавляю флаг, чтобы утверждать, нет псевдонимов -fno-alias
, они есть. Таким образом, я пришел к выводу, что я должен прописать это в источнике, поэтому я пытаюсь сделать это путем компиляции с
$ icc -O3 -std=c99 -opt-report5 -mavx -DARRAY_RESTRICT=restrict -S foo.c
icc: remark #10397: optimization reports are generated in *.optrpt files in the output location
Вывод ошибки компилятора включает
foo.c(98): error: "restrict" is not allowed
void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n],
const double ARRAY_RESTRICT c[n])
^
но, как вы можете видеть, ограничение не разрешено в моих аргументах VLA.
Таким образом, мой вопрос: нет ли способа утверждать, что нет псевдонима VLA в ISO C?
Обратите внимание, что я не могу утверждать, что в исходном коде нет псевдонимов с помощью прагм - например simd
, omp simd
, ivdep
и т.д. - и получите автоматическую векторизацию, которую я хочу, но это не ISO C.
В этом контексте ISO C означает самую последнюю версию C, которая, конечно, на момент написания этого поста была C11.
2 ответа
Ваш оригинальный код не работает для меня с такими сообщениями, как:
void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n])
^
restrict.c:126:1: error: invalid use of ‘restrict’
restrict.c:126:1: error: invalid use of ‘restrict’
restrict.c:145:1: error: invalid use of ‘restrict’
Передача выбранных частей комментариев
§6.7.6.3 Деклараторы функций (включая прототипы) имеют Пример 5, в котором говорится, что следующие деклараторы прототипов функций эквивалентны:
void f(double (* restrict a)[5]);
void f(double a[restrict][5]);
void f(double a[restrict 3][5]);
void f(double a[restrict static 3][5]);
Это единственное место в стандарте, где ограничение появляется, напрямую связанное с типами массивов. §6.7.6 в целом относится к объявлениям, а §6.7.6.2 - к объявлениям массивов, и мне кажется, что ограничение должно появляться внутри первого компонента измерения массива. В вашем контексте это должно быть:
void foo7(int n, double a[ARRAY_RESTRICT n],
const double b[ARRAY_RESTRICT n],
const double c[ARRAY_RESTRICT n])
Я бы не поверил этой записи, не увидев примеры в стандарте и не задав вопрос! Обратите внимание, что это относится как к массивам, так и к VLA.
Этот пересмотренный код, основанный на комментарии, компилируется чисто с теми же параметрами компиляции:
gcc -g -O3 -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
-Wold-style-definition -Wold-style-declaration -Werror -c new.restrict.c
Параметры компиляции требуют предварительного объявления нестатических функций, следовательно, объявления в верхней части файла. Я также вынужден #define ARRAY_RESTRICT restrict
в источнике, а не оставить его в качестве варианта компиляции.
Компилятор - GCC 4.9.2, работающий на производной Ubuntu 14.04.
файл
new.restrict.c
:
/* #define PRAGMA_SIMD _Pragma("simd") */
#define PRAGMA_SIMD
#ifdef __INTEL_COMPILER
#define ASSUME_ALIGNED(a) __assume_aligned(a, 64)
#else
#define ASSUME_ALIGNED(a)
#endif
#define ARRAY_RESTRICT restrict
#ifndef ARRAY_RESTRICT
#define ARRAY_RESTRICT
#endif
void foo1(double *restrict a, const double *restrict b, const double *restrict c);
void foo2(double *restrict a, const double *restrict b, const double *restrict c);
void foo3(int n, double *restrict a, const double *restrict b, const double *restrict c);
void foo4(int n, double *restrict a, const double *restrict b, const double *restrict c);
void foo5(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048]);
void foo6(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048]);
void foo7(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n]);
void foo8(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n]);
void foo1(double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo2(double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Undetermined size version */
void foo3(int n, double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo4(int n, double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Static array versions */
void foo5(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo6(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* VLA versions */
void foo7(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo8(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
Ни один из параметров в этом коде не имеет изменяемого типа. foo6
а также foo7
точно такая же сигнатура функции, за исключением int n
, См. Почему компиляторы C и C++ допускают длину массива в сигнатурах функций, когда они никогда не применяются?,
Это все точно так же:
void foo8(int n, T *a);
void foo8(int n, T a[16]);
void foo8(int n, T a[n]);
Версия void foo8(int n, T a[]);
почти то же самое, но у него есть угловой случай, что это не разрешено, если T
является неполным типом.
foo8
может вызываться с VLA или без VLA.
Несмотря на то, что декларатор массива имеет изменяемый тип, корректировка массива T для указателя на T выполняется до того, как тип параметра будет считаться. Так T a[n]
настроен на T *a
который не изменяется-изменен; тем не мение void foo9(int n, T a[][n]);
действительно дает переменно-модифицированный тип T (*)[n]
за a
,
Самый простой способ объединения restrict
с декларатором массива фактически использовать форму указателя, здесь:
void foo8(int n, T *restrict a ) {
Попытка void foo8(int n, T restrict a[]);
не работает, потому что это эквивалентно void foo8(int n, T restrict *a);
, restrict
является классификатором, и он ведет себя синтаксически так же, как другие классификаторы, такие как const
,
Как отметил Джонатан Леффлер, существует альтернативный синтаксис:
void foo8(int n, T a[restrict]) { // n is optional , as before
В этом случае представляется излишним разрешать указывать одну и ту же вещь двумя различными способами, однако также существуетstatic
который может использоваться только с декларатором массива (не декларатором указателей). Если вы хотите использовать эту форму static
а также restrict
тогда нет другого выбора, кроме как иметь restrict
внутри квадратных скобок:
void foo8(int n, T a[restrict static n]) {
Чтобы быть ясным, этот последний случай все еще не изменяемого типа; static
это обещание, что a
, который является указателем, указывает на первый элемент массива по крайней мере n
элементы.
Так же static
не нужно проверять во время компиляции, когда вызывается функция (хотя, конечно, было бы неплохо, если бы компилятор принудительно применял ее).
Последнее замечание: restrict
в прототипе, вероятно, не имеет никакого эффекта, это важно только в определении функции.