Перейти к содержанию
    

С++ лямбды и многомерные Variable Sized Arrays

Добрый день,

 

есть веками (еще с 1999 года) работающий софт для GNU-C, который интенсивно использует и многомерные массивы переменной длины (как в фортране) и вложенные функции. Про многомерные массивы переменной длины понятно (правда по-английски) написано тут. Пример прилагаю:

#include <math.h>

void Func(int N, int M, double X[N][M])
{

 void Test()
  { int i, j;
    for(i=0; i<N; i++)
      for(j=0; j<M; j++)
        X[i][j]=(double)(i*1000+j);
  }

  int i, j;
  Test();

  for(i=0; i<N; i++)
  { double s=0;
    for(j=0; j<M; j++)
      s+=X[i][j]*X[i][j];
    s=(s>0.)?1./sqrt(s):0.;
    for(j=0; j<M; j++)
      X[i][j]*=s;
  }
  return;
}

Все бы ничего, но вложенные функции за 20 лет с момента появления в GNU-C так и не стали частью С стандарта. Мне хочется перетащить мои программы на что-то стандартное, как вариант, на С++14/С++17, там есть лямбды, аналог вложенных функций.

 

Попробовал в лоб сделать на ламбдах, получилось так:

#include <math.h>

void Func1(int N, int M, double X[N][M]) // ошибка тут, так декларировать в С++ нельзя
{ 
  auto Test = [&] () -> void
  { int i, j;
    for(i=0; i<N; i++)
      for(j=0; j<M; j++)
        X[i][j]=(double)(i*1000+j);
  };

  int i, j;
  Test();

  for(i=0; i<N; i++)
  { double s=0;
    for(j=0; j<M; j++)
      s+=X[i][j]*X[i][j];
    s=(s>0.)?1./sqrt(s):0.;
    for(j=0; j<M; j++)
      X[i][j]*=s;
  }
  return;
}


void Func2(int N, int M, double *_X)
{ double (*X)[M] = static_cast<double (*)[M]>((void*)_X);

  auto Test = [&] () -> void
  { int i, j;
    for(i=0; i<N; i++)
      for(j=0; j<M; j++)
        X[i][j]=(double)(i*1000+j); // почему-то компилер не "видит" правильное описание Х и на него ругается
  };

  int i, j;
  Test();

  for(i=0; i<N; i++)
  { double s=0;
    for(j=0; j<M; j++)
      s+=X[i][j]*X[i][j];
    s=(s>0.)?1./sqrt(s):0.;
    for(j=0; j<M; j++)
      X[i][j]*=s;
  }
  return;
}

void Func3(int N, int M, double *_X)
{ double (*X)[M] = static_cast<double (*)[M]>((void*)_X);

  auto Test = [&] () -> void
  { int i, j;
    double (*X)[M] = static_cast<double (*)[M]>((void*)_X);
    for(i=0; i<N; i++)
      for(j=0; j<M; j++)
        X[i][j]=(double)(i*1000+j); // теперь он все видит, но приходится писать double (*X)[M] ... в каждой лямбде и копировать указатель из _Х
  };

  int i, j;
  Test();

  for(i=0; i<N; i++)
  { double s=0;
    for(j=0; j<M; j++)
      s+=X[i][j]*X[i][j];
    s=(s>0.)?1./sqrt(s):0.;
    for(j=0; j<M; j++)
      X[i][j]*=s;
  }
  return;
}

здесь

Func1 - калька с С понятно не компилируется, так как в аргументах в С++ нельзя вставлять размерности,

Func2 - почти решает проблему, но все равно вылазит ошибка, когда в лямбде идет адресация массива, описанного в головной функции,

Func3 - и только тут с копией в каждой единице функции удается все скомпилировать.

 

Я понимаю, что да, так можно тоже писать, но появляются куча копий названий, если есть несколько ламбд, что очень не приятно.

У меня с пол миллиона строк такого кода, и надо бы принять решение как его правильно перелопатить, поэтому так педантично хочу эту проблему решить.

 

Скажите, пожалуйста, есть ли какой-то способ таки не дублировать в каждой лямбде название этого многомерного массива?

 

Спасибо!

 

ИИВ

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Можно ориентироваться на STL



class C_MBRegs 
{
 public:
  int s_ParID;		// идентификатор параметра (ID) в типе приборов
  int s_RegAddr;        // адрес регистра, по терминологии MODBUS      
  int s_RegSize;	// длина поля данных, в кол-ве регистров начиная с адреса s_RegAddr.   
. . . 
};

#include "stdafx.h"
//#include <list>
#include <vector>
//#include <deque>

using namespace std;
vector<C_MBRegs>RegsBaseVect;	

int main(int argc, char* argv[])
{

    RegsBaseVect.push_back( MB_Data );

	int n_recs = RegsBaseVect.size();

. . . .
  
. . . .
	for(int i=0; i<n_recs; i++) RegsBaseVect[i].DisplayData();
. . . 
}

Попробуйте Ваши лямбды обмануть макросами на новом компиляторе.

А вообще, рассмотрите переделку кода на ООП - вложенные ф-ии неплохо лягут на методы класса.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Аргументы-массивы переменной длинны - часть стандарта C, но не С++. Если вам так хочется, то пользуйтесь третьим вариантом, если хотите - оберните объявление в макрос. Если код работает его лучше не надо "перелопачивать", максимум написать обертку.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасибо k155la3 за ответ. Да, конечно можно и через классы переписать, и макросами. Но тут есть несколько ньюансов:

 

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

2. код огромный, я говорил, там с пол миллиона строк, это огромная библиотека на подобие lapack и math-kernel-library

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

4. и код очень хорошо оптимизирован для высокопроизводительных вычислений с учетом мультритрединга и кешей разного уровня.

 

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

10 minutes ago, Kabdim said:

Аргументы-массивы переменной длинны - часть стандарта C, но не С++. Если вам так хочется, то пользуйтесь третьим вариантом, если хотите - оберните объявление в макрос. Если код работает его лучше не надо "перелопачивать", максимум написать обертку. 

Спасибо Kabdim за ответ, а в С++ разве это не стандарт, или хотя бы

 

double (*X)[M] = static_cast<double (*)[M]>((void*)_X);

 

вроде же стандарт?

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

4 minutes ago, iiv said:

. . . 4. и код очень хорошо оптимизирован для высокопроизводительных вычислений с учетом мультритрединга и кешей разного уровня. . . .

А .... тогда понятно. 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 minutes ago, Kabdim said:

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

а, да, точно, нагуглил... Странно. Использование многомерных массивов стандартизовано в фортране еще в 70-ые годы прошлого века, С99 вроде как-то это перенял, а такой язык, как С++ до сих пор не хочет воткнуть это в стандарт!!! Теперь понятно, почему куча людей так до сих пор не отказалась от фортрана.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Куча людей не отказались от фортрана по совсем другим причинам. Вопрос на который вы не ответили - зачем вам переписывать оптимизированный код на С++?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

59 minutes ago, iiv said:

У меня с пол миллиона строк такого кода, и надо бы принять решение как его правильно перелопатить, поэтому так педантично хочу эту проблему решить.

А не проще оставить С, с массивами переменной длины, и написать небольшой скрипт, который сам перелопатит ваши миллионы строк, и объявленные внутренние функции просто вытащит наружу и приведёт тем самым код в соответствие со стандартом без всяких расширений:

#include <math.h>

static inline void Func_INNER_Test() { int i, j; for(i=0; i<N; i++) for(j=0; j<M; j++) X[i][j]=(double)(i*1000+j); } 

void Func(int N, int M, double X[N][M]){
  int i, j;

  Func_INNER_Test();

  for(i=0; i<N; i++)
  { double s=0;
    for(j=0; j<M; j++)
      s+=X[i][j]*X[i][j];
    s=(s>0.)?1./sqrt(s):0.;
    for(j=0; j<M; j++)
      X[i][j]*=s;
  }
  return;
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

15 minutes ago, _pv said:

А не проще оставить С, с массивами переменной длины, и написать небольшой скрипт, который сам перелопатит ваши миллионы строк, и объявленные внутренние функции просто вытащит наружу и приведёт тем самым код в соответствие со стандартом без всяких расширений:

Спасибо большое за ответы!

 

Так я как раз скрипт и планировал написать, чтоб автоматизировать процесс.

Другое дело, сделать в виде inline - не получится. Во вложенных (nested) функциях основным достоинством было то, что сама эта эта функция видит по умолчанию кучу внутренних переменных, объявленных внутри основной функции. Это оптимизирует расположение данных в стеке (кеш правильнее дергается) а так придется получить функции с кучей входных и выходных аргументов. Это плохо и не читаемо.

 

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

 

33 minutes ago, Kabdim said:

Вопрос на который вы не ответили - зачем вам переписывать оптимизированный код на С++?

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

42 minutes ago, Kabdim said:

Куча людей не отказались от фортрана по совсем другим причинам

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Основные пользователи фортрана сейчас - ученые (и рядом с ними). Это весьма возрастная категория, которая застала Фортран еще когда он был вполне мейнстримом. Главная выгода - экономия на времени переучивания и лучшая ориентированность на область использования. Ученым вообще не нужен язык, который позволит писать более быстрые программы, но с возможностью выстрелить себе в ногу. А с другой стороны интеловый фортран компилятор позволяет приблизится по эффективности к программе на С. Именно поэтому есть Matlab, Numpy, R, Julia, etc.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

17 minutes ago, iiv said:

Другое дело, сделать в виде inline - не получится. Во вложенных (nested) функциях основным достоинством было то, что сама эта эта функция видит по умолчанию кучу внутренних переменных, объявленных внутри основной функции. Это оптимизирует расположение данных в стеке (кеш правильнее дергается) а так придется получить функции с кучей входных и выходных аргументов. Это плохо и не читаемо. 

тогда может руками заинлайнить все внутренние функции, 

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

void Func(int N, int M, double X[N][M]){
  int i, j;
  
#define Func_INNER_Test() { int i, j; for(i=0; i<N; i++) for(j=0; j<M; j++) X[i][j]=(double)(i*1000+j); }

  Func_INNER_Test();

  for(i=0; i<N; i++)
  { double s=0;
    for(j=0; j<M; j++)
      s+=X[i][j]*X[i][j];
    s=(s>0.)?1./sqrt(s):0.;
    for(j=0; j<M; j++)
      X[i][j]*=s;
  }
  return;
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

20 минут назад, iiv сказал:

Я ж в самом начале написал.

Не понял с первого раза.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

11 minutes ago, _pv said:

тогда может руками заинлайнить все внутренние функции,  

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

 

Да и код потом поддерживать надо. Если есть какие-то дефайны с кодом внутри кода, то это - гарантия безобразия в софте через пару лет интенсивной поддержки. Я говорю, код изначально писался еще на фортране в 1993-1996, потом медленно переползал (модифицируясь) на С, потом на С99. Слава Богу там пока говнокода не появилось, и я очень трепетно к этому отношусь.

6 minutes ago, Kabdim said:

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

Спасибо! Да Вы правы, именно такое решение мы рассматриваем, как запасное, если не удастся найти другое менее болезненное решение. Но оно точно потянет перетестирование всей оптимизации кеша и мультитредингов (вложенные функции ужасно удобны в ручном мультитрединге). Это к сожалению, на человекогод работы точно потянет, если не больше.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...