Распространенная ситуация при программинге, как быть? Быстродействие vs Лаконичность

 

Вечная дилема между красивым/лаконичным кодом и быстродействующим у каждого решается по-своему. Очень часто многое отдается на откуп компилятору с надеждой, что лаконичный код будет компилятором прооптимизирован (через инлайнинг и прочее) до состояния быстродействующего варианта.

 

Такие ситуации встречаются довольно часто. Вот пример довольно распространенного случая:

#property strict

#define AMOUNT 1000000

struct STRUCT1
{
  int bid, ask;
};

struct STRUCT2
{
  int min, max;
};

STRUCT1 Source1[AMOUNT];
STRUCT2 Source2[AMOUNT];

void GetNums( const bool FlagSource, const int Pos, int &Num1, int &Num2 )
{
  if (FlagSource)
  {
    Num1 = Source1[Pos].bid;
    Num2 = Source1[Pos].ask;
  }
  else
  {
    Num1 = Source2[Pos].min;
    Num2 = Source2[Pos].max;
  }
  
  return;
}

// Красивый/лаконичный код
void Calculate1( const bool FlagSource )
{
// здесь определяется много переменных для дальнейших вычислений
// ....

  int Num1, Num2;
  
  for (int i = 0; i < AMOUNT; i++)
  {
    GetNums(FlagSource, i, Num1, Num2);
    
// здесь большой кусок вычислительного кода, использующий Num1 и Num2 и ранее заданные переменные
// поэтому вынести его в отдельную функцию (все равно проинлайнится при компилировании) крайне проблематично
// - понадобится прописывать очень много входных параметров.
// ....
  }
  
  return;
}

// Быстродействующий код
void Calculate2( const bool FlagSource )
{
// здесь определяется много переменных для дальнейших вычислений
// ....

  int Num1, Num2;
  
  if (FlagSource)
    for (int Pos = 0; Pos < AMOUNT; Pos++)
    {
      Num1 = Source1[Pos].bid;
      Num2 = Source1[Pos].ask;
      
  // здесь большой кусок кода, использующий Num1 и Num2 и ранее заданные переменные
  // поэтому вынести его в отдельную функцию (все равно проинлайнится при компилировании) крайне проблематично
  // - понадобится прописывать очень много входных параметров.
  // ....
    }
  else
    for (int Pos = 0; Pos < AMOUNT; Pos++)
    {
      Num1 = Source2[Pos].min;
      Num2 = Source2[Pos].max;
      
  // здесь большой кусок кода, использующий Num1 и Num2 и ранее заданные переменные
  // поэтому вынести его в отдельную функцию (все равно проинлайнится при компилировании) крайне проблематично
  // - понадобится прописывать очень много входных параметров.
  
  // ....
    }
  
  return;
}

void OnStart( void )
{  
  if (MathRand() > (SHORT_MAX >> 1))
  {
    Calculate1(TRUE);
    Calculate1(FALSE);
  }
  else
  {
    Calculate2(TRUE);
    Calculate2(FALSE);
  }
 
// результаты работы Calculate1 и Calculate2 буду идентичны, но скорость выполнения разная:
// скорость Calculate2 будет выше Calculate2, т.к. не будет выполняться лишний if внутри Pos-цикла.
// но запись Calculate1 гораздо лаконичнее, красивее и удобнее.
// Как в подобных ситуациях вы поступаете? Хочется, чтобы запись была и лаконичной и быстрой.
// Может, макрос, а не функцию применять, чтобы огромное количество входных параметров не передавать...
  
  return;
}

Пояснения и вопросы в комментариях. Кракто, как в такой ситуации добиться быстродействия и не потерять в стройности кода? Пришло на ум использование макросов.

 

В общем, поделититесь своим опытом и рекомендациями, как действовать, когда, например, всего лишь из-за одного if (как в примере) приходится для быстродействия копи-пастить огромную часть кода, создавая монстрский код.


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

 

Не парьтесь, пишите как понятней вам. Нормальный оптимизатор изменит код до неузнаваемости.

Для проверки могу порекомендовать поэкспериментировать на gcc (mingw на Windows). В командной строке:

gсс source.cpp -o out.txt -S             неоптимизированный код
gсс source.cpp -o out.txt -S -O3         оптимизированный код

Получится ассемблерный код после оптимизации/без оптимизации.

mingw http://sourceforge.net/projects/mingw/files/latest/download?source=files

 
Конечно, тонкости есть, например такая http://alenacpp.blogspot.ru/2008/02/rvo-nrvo.html.
 
Pavlick:

Не парьтесь, пишите как понятней вам. Нормальный оптимизатор изменит код до неузнаваемости.

Для проверки могу порекомендовать поэкспериментировать на gcc (mingw на Windows). В командной строке:

Получится ассемблерный код после оптимизации/без оптимизации.

mingw http://sourceforge.net/projects/mingw/files/latest/download?source=files

К сожалению, я очень далек от программирования и ассемблерный код не понимаю. Поэтому готов верить мнению профессионалов.

Можно ли утвреждать, что при оптимизации в Calculate1 будет заинлайнен GetNums таким образом, что if внутри него выполнится только один раз?

 
Вы перезагоняетесь. Конечно 1 вариант.
 
lob32371:

Вечная дилема между красивым/лаконичным кодом и быстродействующим у каждого решается по-своему. Очень часто многое отдается на откуп компилятору с надеждой, что лаконичный код будет компилятором прооптимизирован (через инлайнинг и прочее) до состояния быстродействующего варианта.

 

Такие ситуации встречаются довольно часто. Вот пример довольно распространенного случая:

Пояснения и вопросы в комментариях. Кракто, как в такой ситуации добиться быстродействия и не потерять в стройности кода? Пришло на ум использование макросов.

 

В общем, поделититесь своим опытом и рекомендациями, как действовать, когда, например, всего лишь из-за одного if (как в примере) приходится для быстродействия копи-пастить огромную часть кода, создавая монстрский код.


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

 

Если быстродействие критично, забейте на лаконичность.

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

 
lob32371:

К сожалению, я очень далек от программирования и ассемблерный код не понимаю. Поэтому готов верить мнению профессионалов.

Можно ли утвреждать, что при оптимизации в Calculate1 будет заинлайнен GetNums таким образом, что if внутри него выполнится только один раз?

Я не могу говорить за MQ компилятор, c gcc получилось следующее:

_Calculate1:
        ...
        cmpb    $0, 32(%esp)                    # если FlagSource != 0,
        jne     L13                             # то уходим на L13.
	...
L12:                                            # FlagSource == 0.
        movl    _Source2+4(,%ebx,8), %eax       # читаем Num1
        ...                                     # и
        movl    _Source2(,%ebx,8), %eax         # Num2.
        ...
        cmpl    $1000000, %ebx
        jne     L12                             # если i не 1000000, то прыгаем на L12.
L9:
        ...
        ret                                     # выходим из функции.
        ...
L13:                                            # FlagSource == 1.
        ...
        movl    _Source1+4(,%ebx,8), %eax       # читаем Num1
        ...                                     # и
        movl    _Source1(,%ebx,8), %eax         # Num2.
        ...
        cmpl    $1000000, %ebx
        jne     L13                             # если i не 1000000, то прыгаем на L13.
        jmp     L9                              # прыгаем на выход.

GetNums() заинлайнилась.

 
TheXpert:
Вы перезагоняетесь. Конечно 1 вариант.
Мощь русского языка иногда мне не подвластна. Спасибо за мнение, учту.
Ko1dun:

Если быстродействие критично, забейте на лаконичность.

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

Конечно, здесь ресь не идет о критичности скорости выполнения - иначе выбирать не приходилось бы.

Внутри сидит какой-то глист-эстет (с раздвоением личности), который удовлетворен бывает только тогда, когда лаконичность и быстродействие находятся в разумном консенсусе.

В общем, если мне скажут что это имеет место быть:

lob32371:

... при оптимизации в Calculate1 будет заинлайнен GetNums таким образом, что if внутри него выполнится только один раз

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

Я не могу говорить за MQ компилятор, c gcc получилось следующее:

GetNums() заинлайнилась.

Заинлайнилась - это хорошо! Не понял только, if выполнится один раз или с каждом шагом в цикле for?

ЗЫ Заинлайнилась - моща у русского языка еще та! И ведь все понимают. И по филологическим правилам. Pavlick, красава!

 
После оптимизации Calculate1() == Calculate2()
 
Pavlick:
После оптимизации Calculate1() == Calculate2()
Спасибо, Добр Человек! Буду, не смущаясь, лепить правду-матку через красивый/лаконичный код.