Вручную преобразуйте алгоритм MATLAB с плавающей точкой в фиксированную точку

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

  • Разделите свой алгоритм от тестового файла.

  • Подготовьте свой алгоритм к инструментированию и генерации кода.

  • Справьтесь с ростом бита управления и типами данных.

  • Отдельные определения типов от алгоритмического кода путем составления таблицы определений данных.

Для полного списка лучшых практик смотрите Ручные Лучшые практики Преобразования Фиксированной точки.

Разделите свой алгоритм от тестового файла

Запишите функцию MATLAB®, mysum, который суммирует элементы вектора.

function y = mysum(x)
  y = 0;
  for n = 1:length(x)
    y = y + x(n);
  end
end

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

Запишите тестовый скрипт

В тестовом файле создайте свои входные параметры, вызовите алгоритм и постройте результаты.

  1. Запишите скрипт MATLAB, mysum_test, который проверяет поведение вашего алгоритма с помощью двойных типов данных.

    n = 10;
    rng default
    x = 2*rand(n,1)-1;
    
    % Algorithm
    y = mysum(x);
    
    % Verify results
    y_expected = sum(double(x));
    
    err = double(y) - y_expected

    rng default помещает настройки генератора случайных чисел, используемого функцией rand к ее значению по умолчанию так, чтобы это произвело те же случайные числа, как будто вы перезапустили MATLAB.

  2. Запустите тестовый скрипт.

    mysum_test
    err =
    
         0

    Результаты, полученные с помощью mysum, совпадают с теми полученное использование функции sum MATLAB.

Для получения дополнительной информации смотрите, Создают Тестовый файл.

Подготовьте алгоритм к инструментированию и генерации кода

В вашем алгоритме, после функциональной подписи, добавляет директива компиляции %#codegen, чтобы указать, что вы намереваетесь оснастить алгоритм и сгенерировать код С для него. Добавление этой директивы дает анализатору кода MATLAB команду помогать вам диагностировать и зафиксировать нарушения, которые привели бы к ошибкам во время инструментирования и генерации кода.

function y = mysum(x) %#codegen
  y = 0;  
  for n = 1:length(x)
    y = y + x(n);
  end
end

Для этого алгоритма индикатор анализатора кода в правом верхнем углу окна редактора остается зеленое сообщение вам, что это не обнаружило проблем.

Для получения дополнительной информации смотрите, Готовят Ваш Алгоритм к Ускорению Кода или Генерации кода.

Сгенерируйте код С для своего исходного алгоритма

Сгенерируйте код С для исходного алгоритма, чтобы проверить, что алгоритм подходит для генерации кода и видеть код С с плавающей точкой. Используйте функцию codegen (требует MATLAB Coder™) сгенерировать библиотеку C.

  1. Добавьте следующую строку в конец своего тестового скрипта, чтобы сгенерировать код С для mysum.

    codegen mysum -args {x} -config:lib -report

  2. Запустите тестовый скрипт снова.

    MATLAB Coder генерирует код С для функции mysum и обеспечивает ссылку на отчет генерации кода.

  3. Щелкните по ссылке, чтобы открыть отчет генерации кода и просмотреть сгенерированный код C для mysum.

    /* Function Definitions */
    double mysum(const double x[10])
    {
      double y;
      int n;
      y = 0.0;
      for (n = 0; n < 10; n++) {
        y += x[n];
     }
     
     return y;
     }

    Поскольку C не позволяет индексы с плавающей точкой, счетчик цикла, n, автоматически объявляется как целочисленный тип. Вы не должны преобразовывать n в фиксированную точку.

    Введите x, и вывод y объявляются как дважды.

Справьтесь с ростом бита управления и типами данных

Протестируйте свой алгоритм с одиночными играми, чтобы проверять на несоответствия типов

  1. Измените свой тестовый файл так, чтобы тип данных x был одним.

    n = 10;
    rng default
    x = single(2*rand(n,1)-1);
    
    % Algorithm
    y = mysum(x);
    
    % Verify results
    y_expected = sum(double(x));
    
    err = double(y) - y_expected
    codegen mysum -args {x} -config:lib -report

  2. Запустите тестовый скрипт снова.

    mysum_test
    err =
    
      -4.4703e-08
    
    ??? This assignment writes a 'single' value into a 'double' type. Code generation
    does not support changing types through assignment. Check preceding assignments or
    input type specifications for type mismatches.

    Сбои генерации кода, сообщая о неверном типе данных относительно строки y = y + x(n);.

  3. Чтобы просмотреть ошибку, откройте отчет.

    В отчете, на строке y = y + x(n), отчет подсвечивает y на левой стороне присвоения красного цвета, чтобы указать, что существует ошибка. Проблема - то, что y объявляется как двойное, но присваивается синглу. y + x(n) является суммой двойного и сингла, который является синглом. Если вы устанавливаете свой курсор на переменные и выражения в отчете, вы видите информацию об их типах. Здесь, вы видите, что выражение, y + x(n) является синглом.

  4. Чтобы зафиксировать несоответствие типов, обновите свой алгоритм, чтобы использовать преобразованное в нижний индекс присвоение за сумму элементов. Измените y = y + x(n) на y(:) = y + x(n).

    function y = mysum(x) %#codegen
      y = 0;
      for n = 1:length(x)
        y(:) = y + x(n);
      end
    end
    

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

  5. Регенерируйте код С и откройте отчет генерации кода. В коде С результат теперь брошен, чтобы удвоиться, чтобы разрешить несоответствие типов.

Сборка оснащенный Mex

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

  1. Обновите тестовый скрипт:

    1. После того, как вы объявите n, добавьте buildInstrumentedMex mySum —args {zeros(n,1)} -histogram.

    2. Возвратите x, чтобы удвоиться. Замените x = single(2*rand(n,1)-1); на x = 2*rand(n,1)-1;

    3. Вместо того, чтобы вызвать исходный алгоритм, вызовите сгенерированную MEX-функцию. Измените y = mysum(x) на y=mysum_mex(x).

    4. После вызова MEX-функции добавьте showInstrumentationResults mysum_mex -defaultDT numerictype(1,16) -proposeFL. Флаги -defaultDT numerictype(1,16) -proposeFL указывают, что вы хотите предложить дробные длины для 16-битного размера слова.

      Вот обновленный тестовый скрипт.

      %% Build instrumented mex
      n = 10;
      
      buildInstrumentedMex mysum -args {zeros(n,1)} -histogram
      
      %% Test inputs
      rng default
      x = 2*rand(n,1)-1;
      
      % Algorithm
      y = mysum_mex(x);
      
      % Verify results
      
      showInstrumentationResults mysum_mex ...
        -defaultDT numerictype(1,16) -proposeFL
      y_expected = sum(double(x));
      
      err = double(y) - y_expected
      
      %% Generate C code
      
      codegen mysum -args {x} -config:lib -report
      

  2. Запустите тестовый скрипт снова.

    Функция showInstrumentationResults предлагает типы данных и открывает отчет отобразить результаты.

  3. В отчете кликните по вкладке Variables. showInstrumentationResults предлагает дробную длину 13 для y и 15 для x.

В отчете вы можете:

  • Просмотрите симуляцию минимальные и максимальные значения для входа x и вывода y.

  • Просмотрите предложенные типы данных для x и y.

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

    Чтобы просмотреть эту информацию, установите свой курсор на переменную или выражение в отчете.

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

    Чтобы просмотреть гистограмму для конкретной переменной, кликните по ее иконке гистограммы.

Отдельные определения типов от алгоритмического кода

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

Измените mysum так, чтобы он взял входной параметр, T, который является структурой, которая задает типы данных входных и выходных данных. Когда y будет сначала задан, используйте функцию cast как синтаксис — cast(x,'like',y) — чтобы бросить x к желаемому типу данных.

function y = mysum(x,T) %#codegen
  y = cast(0,'like',T.y);
  for n = 1:length(x)
    y(:) = y + x(n);
  end
end

Составьте таблицу определений типов

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

  • Протестируйте связь между таблицей определения типов, и ваше использование алгоритма удваивается.

  • Протестируйте алгоритм с одиночными играми, чтобы найти неверные типы данных и другие проблемы.

  • Запуститесь масштабируемое использование алгоритма удваивается, чтобы проверять на переполнение.

function T = mytypes(dt)
  switch dt
    case 'double'
      T.x = double([]);
      T.y = double([]);
    case 'single'
      T.x = single([]);
      T.y = single([]);
    case 'fixed'
      T.x = fi([],true,16,15);
      T.y = fi([],true,16,13);
    case 'scaled'
      T.x = fi([],true,16,15,...
           'DataType','ScaledDouble');
      T.y = fi([],true,16,13,...
           'DataType','ScaledDouble');
  end
end

Для получения дополнительной информации смотрите Отдельные Определения типов из Алгоритма.

Обновите тестовый скрипт, чтобы использовать таблицу типов

Обновите тестовый скрипт, mysum_test, чтобы использовать таблицу типов.

  1. Для первого показа проверяйте, что связь между использованием таблицы и алгоритма удваивается. Прежде чем вы объявите n, добавьте T = mytypes('double');

  2. Обновите вызов buildInstrumentedMex, чтобы использовать тип T.x, заданного в таблице типов данных: buildInstrumentedMex mysum -args {zeros(n,1,'like',T.x),T} -histogram

  3. Бросьте x, чтобы использовать тип T.x, заданного в таблице: x = cast(2*rand(n,1)-1,'like',T.x);

  4. Вызовите передачу MEX-функции в T: y = mysum_mex(x,T);

  5. Вызовите передачу codegen в T: codegen mysum -args {x,T} -config:lib -report

    Вот обновленный тестовый скрипт.

    %% Build instrumented mex
    T = mytypes('double');
    
    n = 10;
    
    buildInstrumentedMex mysum ...
        -args {zeros(n,1,'like',T.x),T} -histogram
    
    %% Test inputs
    rng default
    x = cast(2*rand(n,1)-1,'like',T.x);
    
    % Algorithm
    y = mysum_mex(x,T);
    
    % Verify results
    
    showInstrumentationResults mysum_mex ...
        -defaultDT numerictype(1,16) -proposeFL
    
    y_expected = sum(double(x));
    
    err = double(y) - y_expected
    
    %% Generate C code
    
    codegen mysum -args {x,T} -config:lib -report
    

  6. Запустите тестовый скрипт и щелкните по ссылке, чтобы открыть отчет генерации кода.

    Сгенерированный код C совпадает с кодом, сгенерированным для исходного алгоритма. Поскольку переменная T используется, чтобы задать типы, и эти типы являются постоянными во время генерации кода; T не используется во время выполнения и не появляется в сгенерированном коде.

Сгенерируйте фиксированную точку

Обновите тестовый скрипт, чтобы использовать фиксированные точки, предложенные ранее и просмотреть сгенерированный код C.

  1. Обновите тестовый скрипт, чтобы использовать фиксированные точки. Замените T = mytypes('double'); на T = mytypes('fixed'); и затем сохраните скрипт.

  2. Запустите тестовый скрипт и просмотрите сгенерированный код C.

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

Оптимизируйте типы данных

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

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

  1. Обновите тестовый скрипт, чтобы использовать масштабируемый, удваивается. Замените T = mytypes('fixed'); на T = mytypes('scaled');

  2. Запустите тестовый скрипт снова.

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

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

  3. Найдите полный спектр входа.

    range(T.x)
    -1.000000000000000   0.999969482421875
    
              DataTypeMode: Fixed-point: binary point scaling
                Signedness: Signed
                WordLength: 16
            FractionLength: 15

  4. Обновите скрипт, чтобы протестировать отрицательный случай ребра. Запустите mysum_mex с исходным случайным входом и с входом, который тестирует полный спектр, и агрегируйте результаты.

    %% Build instrumented mex
    T = mytypes('scaled');
    n = 10;
    
    buildInstrumentedMex mysum ...
        -args {zeros(n,1,'like',T.x),T} -histogram
    
    %% Test inputs
    rng default
    x = cast(2*rand(n,1)-1,'like',T.x);
    y = mysum_mex(x,T);
     % Run once with this set of inputs
    y_expected = sum(double(x));
    err = double(y) - y_expected
    
    % Run again with this set of inputs. The logs will aggregate.
    x = -ones(n,1,'like',T.x);
    y = mysum_mex(x,T); 
    y_expected = sum(double(x));
    err = double(y) - y_expected 
    
    % Verify results
    
    showInstrumentationResults mysum_mex ...
        -defaultDT numerictype(1,16) -proposeFL
    
    y_expected = sum(double(x));
    
    err = double(y) - y_expected
    
    %% Generate C code
    
    codegen mysum -args {x,T} -config:lib -report
    

  5. Запустите тестовый скрипт снова.

    Тестовые прогоны и y переполняют области значений типа данных с фиксированной точкой. showInstrumentationResults предлагает новую дробную длину 11 для y.

  6. Обновите тестовый скрипт, чтобы использовать масштабируемый, удваивается с новым предложенным типом для y. В myTypes.m, для случая 'scaled', T.y = fi([],true,16,11,'DataType','ScaledDouble')

  7. Повторно выполните тестовый скрипт.

    Нет теперь никакого переполнения.

Сгенерируйте код для предложенной фиксированной точки

Обновите таблицу типов данных, чтобы использовать предложенную фиксированную точку и сгенерировать код.

  1. В myTypes.m, для случая 'fixed', T.y = fi([],true,16,11)

  2. Обновите тестовый скрипт, mysum_test, чтобы использовать T = mytypes('fixed');

  3. Запустите тестовый скрипт и затем щелкните по ссылке Отчета Представления, чтобы просмотреть сгенерированный код C.

    short mysum(const short x[10])
    {
      short y;
      int n;
      int i0;
      int i1;
      int i2;
      int i3;
      y = 0;
      for (n = 0; n < 10; n++) {
        i0 = y << 4;
        i1 = x[n];
        if ((i0 & 1048576) != 0) {
          i2 = i0 | -1048576;
        } else {
          i2 = i0 & 1048575;
       }
       
        if ((i1 & 1048576) != 0) {
         i3 = i1 | -1048576;
        } else {
          i3 = i1 & 1048575;
        }
    
      i0 = i2 + i3;
      if ((i0 & 1048576) != 0) {
        i0 |= -1048576;
      } else {
        i0 &= 1048575;
      }
    
      i0 = (i0 + 8) >> 4;
      if (i0 > 32767) {
        i0 = 32767;
      } else {
          if (i0 < -32768) {
            i0 = -32768;
          }
        }
    
       y = (short)i0;
      }
      return y;
    }

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

Измените fimath Настройки

Чтобы сделать сгенерированный код более эффективным, используйте математические настройки (fimath) фиксированной точки, которые более подходят для генерации кода C: перенеситесь на округлении пола и переполнении.

  1. В myTypes.m добавьте случай 'fixed2':

     case 'fixed2'
          F = fimath('RoundingMethod', 'Floor', ...
               'OverflowAction', 'Wrap', ...
               'ProductMode', 'FullPrecision', ...
               'SumMode', 'KeepLSB', ...
               'SumWordLength', 32, ...
               'CastBeforeSum', true);
          T.x = fi([],true,16,15,F);
          T.y = fi([],true,16,11,F);
    

    Совет

    Вместо того, чтобы вручную ввести свойства fimath, можно использовать редактора MATLAB опция Insert fimath. Для получения дополнительной информации смотрите Создание fimath Конструкторы Object в графический интерфейсе пользователя.

  2. Обновите тестовый скрипт, чтобы использовать 'fixed2', запустить скрипт, и затем просмотреть сгенерированный код C.

    short mysum(const short x[10])
    {
     short y;
     int n;
     y = 0;
     for (n = 0; n < 10; n++) {
       y = (short)(((y << 4) + x[n]) >> 4);
     }
    
      return y;
    }

    Сгенерированный код более эффективен, но y переключен, чтобы выровняться с x и теряет 4 бита точности.

  3. Чтобы зафиксировать эту потерю точности, обновите размер слова y к 32 битам и сохраните 15 битов точности, чтобы выровняться с x.

    В myTypes.m добавьте случай 'fixed32':

     case 'fixed32'
          F = fimath('RoundingMethod', 'Floor', ...
               'OverflowAction', 'Wrap', ...
               'ProductMode', 'FullPrecision', ...
               'SumMode', 'KeepLSB', ...
               'SumWordLength', 32, ...
               'CastBeforeSum', true);
          T.x = fi([],true,16,15,F);
          T.y = fi([],true,32,15,F);
    

  4. Обновите тестовый скрипт, чтобы использовать 'fixed32' и запустить скрипт, чтобы сгенерировать код снова.

    Теперь, сгенерированный код очень эффективен.

    int mysum(const short x[10])
    {
      int y;
      int n;
      y = 0;
      for (n = 0; n < 10; n++) {
        y += x[n];
      }
     
      return y;
    }

Для получения дополнительной информации смотрите, Оптимизируют Ваш Алгоритм.