Deutsch
Germany.ruФорумы → Архив Досок→ Программирование

Как сконвертировать GNU-C вложенные функции в лямбды С++ и чтоб переносимо

731  
  ilghiz знакомое лицо28.01.19 13:57
28.01.19 13:57 

Добрый день,


есть тонна своего кода на GNU-C, который использует вложенные функции (nested functions), которые так и не воткнули в стандарт. Решил таки перетащить это все на стандартный С++. Всегда думал, что ламбды в С++ - это почти то же самое, что и вложенные функции. Уткнулся, что не могу скомпилировать вроде бы тривиальщину:


void Func(int N, int M, double *_X)
{ double (*X)[M] = static_cast<double (*)[M]>((void*)_X);
int i, j;
double (*Tester1)(int, int);
double (*Tester2)(double *);



Tester1 = [&] (int k, int l) -> double
{ return (double)(k*1000+l); };



Tester2 = [&] (double *XX) -> double
{ int i; double r; for(i=1; i<M; i++) r+=XX*XX; return r; };



for(i=0; i<N; i++)
for(j=0; j<M; j++)
X[j]=Tester1(i,j); // i*1000+j;



for(i=0; i<N; i++)
X[0]=Tester2(X+1);
return;
}


причем первая лямбда (Tester1) компилируется, а вторая - нет что типа не может сконвертировать тип.


Подскажите, пожалуйста, что я делаю не так?


Спасибо!

#1 
  ilghiz знакомое лицо28.01.19 18:50
NEW 28.01.19 18:50 
в ответ ilghiz 28.01.19 13:57

PS: странно как-то текст вставился, в некоторых местях квадратные скобки пропущены, приложил исходник как есть.


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

#2 
LifeRider постоялец29.01.19 16:13
LifeRider
NEW 29.01.19 16:13 
в ответ ilghiz 28.01.19 13:57
Подскажите, пожалуйста, что я делаю не так?

Так просто, без извращений, насколько я знаю, сделать нельзя, т.к. по стандарту C++11, lambda can only be converted to a function pointer if it does not capture... В первом случае (Tester1) компилируется, поскольку де-факто capture и нет, так что [&] не имеет эффекта, а во втором случае это уже не так. :))

#3 
LifeRider постоялец29.01.19 16:59
LifeRider
NEW 29.01.19 16:59 
в ответ ilghiz 28.01.19 13:57
В класс засовывать - совсем не вариант, и писанины много...

double (*Tester1)(int, int);

double (*Tester2)(double *);

auto Tester1 = [&] (int k, int l) -> double {...};

auto Tester2 = [&] (double *XX) -> double {...};

остальное - без изменений

Да уж, писанины и вправду много.:))

#4 
  ilghiz знакомое лицо29.01.19 21:35
NEW 29.01.19 21:35 
в ответ LifeRider 29.01.19 16:59

Спасибо большое, LifeRider, да, про auto я-то и подзабыл в этом контексте. Суммарно получается нестед в такие лямбды можно чуть ли не парсером перетащить, что очень актуально при 10 МБ сорсов.

#5 
dymanoid знакомое лицо29.01.19 22:40
dymanoid
NEW 29.01.19 22:40 
в ответ ilghiz 28.01.19 13:57, Последний раз изменено 29.01.19 22:41 (dymanoid)

Лямбды в плюсах - хорошо, что появились. Но после шарпов как-то корёжит, глядя на синтаксис. С непривычки что ли. Всё никак не могу подружиться с плюсовым синтаксисом лямбд, с самого 11-го стандарта.

#6 
  ilghiz знакомое лицо31.01.19 09:15
NEW 31.01.19 09:15 
в ответ dymanoid 29.01.19 22:40

> Но после шарпов как-то корёжит, глядя на синтаксис.

В плюсах получается описание через auto достаточно лаконичное и от nested functions мало отличающееся. Скажите, пожалуйста, чем шарповские лямбды более привлекательны? Я в шарпе никогда не писал, просто любопытно.

#7 
  moose старожил01.02.19 22:51
NEW 01.02.19 22:51 
в ответ ilghiz 31.01.19 09:15

не обязательно писать, можно просто почитать. задать в гоголе "c# lambda examples", и выбрать что попонятнее. и будет у вас собственное мнение.

#8 
  ilghiz знакомое лицо02.02.19 11:42
NEW 02.02.19 11:42 
в ответ moose 01.02.19 22:51

> не обязательно писать, можно просто почитать. задать в гоголе "c# lambda examples", и выбрать что попонятнее. и будет у вас собственное мнение.

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

#9 
dymanoid знакомое лицо02.02.19 12:49
dymanoid
NEW 02.02.19 12:49 
в ответ ilghiz 31.01.19 09:15

Дело в синтаксисе. Слишком много символов, глаза разбегаются улыб

auto tester1 = [&] (int k, int l) mutable noexcept -> double {...};

vs

var tester1 = (int k, int l) => {...};

Понятно, что некоторые вещи опциональны, но всё же.

#10 
MrSanders старожил02.02.19 15:52
NEW 02.02.19 15:52 
в ответ dymanoid 02.02.19 12:49, Последний раз изменено 02.02.19 15:53 (MrSanders)

н.п.

А почему вы, сионисты, упорно во всех примерах передаете лямбдам ссылки на все внешние переменные? Почему [&] а не просто []? На всякий случай, чтоб было?

#11 
  ilghiz знакомое лицо02.02.19 18:27
NEW 02.02.19 18:27 
в ответ MrSanders 02.02.19 15:52

> А почему вы, сионисты, упорно во всех примерах передаете лямбдам ссылки на все внешние переменные? Почему [&] а не просто []? На всякий случай, чтоб было?


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


В общем случае - не спорю, наличие возможности "видеть" только подкласс переменных - правильнее с точки зрения идеологии.

#12 
LifeRider постоялец02.02.19 18:56
LifeRider
NEW 02.02.19 18:56 
в ответ ilghiz 02.02.19 18:27
А почему вы, сионисты, упорно во всех примерах передаете лямбдам ссылки на все внешние переменные?

И чего это сразу - сионисты, наше дело auto и [&] написать, а дальше - компилятор умный, он пусть думает... Он вот в первом примере сам догадался, что лямбда с [&] - это от лукавого, и родил кондовый function pointer, я современных плюсовых компиляторов уже и побаиваться стал. :))

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

Ну да, производительность от использования лямбд не должна падать, хоть лямбды по-сути есть конвертация в класс, но при этом они создаются в стеке и конструктор класса - inlined. Но, сравнительно потестировать тайминги вызовов не мешало бы. Вот тут https://web.mst.edu/~nmjxv3/articles/lambdas.html немного об оверхеде лямбд в плюсах.

#13 
  ilghiz знакомое лицо02.02.19 19:27
NEW 02.02.19 19:27 
в ответ LifeRider 02.02.19 18:56

> Но, сравнительно потестировать тайминги вызовов не мешало бы. Вот тут https://web.mst.edu/~nmjxv3/articles/lambdas.html немного об оверхеде лямбд в плюсах.


спасибо большое за интересную и познавательную ссылку! Тестировать-то буду однозначно. Я сейчас в процессе принятия решения о перелопачивании огромного куска кода с С на С++. Код изначально шел с фортрана, но много раз частично переписывался и улучшался. Единственно - все было завязано на многомерные массивы в аргументах и вложенные функции. Ждал с 2000 года, чтоб вложенные фунции внесли в стандарт, но этого так и не случилось до настоящего времени, вот и планировал перетащить это все на С++. Код весь в нашей команде разрабатывался, с очень сильной стандартизацией как писать и как располагать данные и с сильным использованием мультитрединга. Несколько раз до этого видел как перекомпиляция с С на С++ ухудшала производительность, поэтому всегда с опаской смотрел на авантюру перетащить этот код с С на С++.

#14 
AlexOtt местный житель02.02.19 20:02
AlexOtt
NEW 02.02.19 20:02 
в ответ ilghiz 02.02.19 19:27

если код с фортрана, то с большой вероятностью это рассчеты. не пробовали переписать на Julia? (https://julialang.org/)

#15 
LifeRider постоялец02.02.19 20:23
LifeRider
NEW 02.02.19 20:23 
в ответ AlexOtt 02.02.19 20:02, Последний раз изменено 02.02.19 20:34 (LifeRider)

Вставлю свои 5 копеек, хотя вопрос не ко мне. :))

Посмотрел бенчмарки Julia, сравнение с Fortran там, на мой взгляд, некорректное, т.к. gnu-компилятор для фортрана - это ни о чем, там разрыв в производительности с Intel Fortran Compiler(IFC)/Portland Group Compiler (PGI) - громадный... По-моему код, скомпилированный PGI был сильно быстрее, точно не помню

#16 
  ilghiz знакомое лицо03.02.19 11:28
NEW 03.02.19 11:28 
в ответ LifeRider 02.02.19 20:23, Последний раз изменено 03.02.19 15:36 (ilghiz)

Спасибо за интересные обсуждения и советы!


> gnu-компилятор для фортрана - это ни о чем, там разрыв в производительности с Intel Fortran Compiler(IFC)/Portland Group Compiler (PGI) - громадный... По-моему код, скомпилированный PGI был сильно быстрее, точно не помню


да, сейчас это верно. Был довольно большой промежуток времени (начало 2000) когда гнутый компилер уступал на проценты, но гемор по подцеплянию icc/pgcc на различные кластерные платформы был тот еще, то есть суммарно GNU был и удобнее, и портабельнее, и, при понимании специфики не уступал по производительности платным компиляторам . Тогда мы и завязались на GNU. Завязавшись на него, захотелось пользовать весь функционал, а конкретно передачу многомерных массивов по аргументам функций (чтоб от Фортрана отказаться) и использование вложенных функций. Наличие этих возможностей позволяло писать программы и с большей скоростью отладки и с большой скоростью работы по сравнению с остальными компиллерами, которые этот функционал не поддерживали. Была надежда, что этот функционал рано или поздно появится в стандарте С и его начнут поддерживать остальные компиляторы. Вот долгое время наш софт так в GNU сидел и продолжал развиваться.


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

#17