Jump to content

    

С++ лямбды и многомерные 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 - и только тут с копией в каждой единице функции удается все скомпилировать.

 

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

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

 

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

 

Спасибо!

 

ИИВ

 

Share this post


Link to post
Share on other sites

Можно ориентироваться на 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();
. . . 
}

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

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

 

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

Спасибо 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 компилируется, и теоретически вроде бы это и есть решение, но очень кривое, так как пространство переменных будет постоянно забиваться дополнительными копиями одномерных вариантов, и этого-то и хотелось бы избежать.

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
4 minutes ago, iiv said:

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

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

Share this post


Link to post
Share on other sites
3 minutes ago, Kabdim said:

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
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;
}

Share this post


Link to post
Share on other sites
15 minutes ago, _pv said:

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

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

 

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

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

 

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

 

33 minutes ago, Kabdim said:

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

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

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

Share this post


Link to post
Share on other sites
42 minutes ago, Kabdim said:

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
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;
}

 

Share this post


Link to post
Share on other sites
20 минут назад, iiv сказал:

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

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

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

Share this post


Link to post
Share on other sites
11 minutes ago, _pv said:

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

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

 

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

6 minutes ago, Kabdim said:

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

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

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now