DSP на Java

    Java — язык программирования общего назначения. Общего назначения — значит можно писать почти любые программы. Вот я и попытался написать программу, которую обычно пишут на С или C++. Под катом я попытаюсь рассказать, как я декодировал спутниковые снимки с Метеор-М №2.


    DSP in Java


    Предисловие


    Когда я впервые заинтересовался декодированием спутниковых сигналов, то был, прямо говоря, удивлён. Сейчас софт для декодирования сигналов выглядит так же, как и библиотеки общего назначения лет 20 назад. Каждый пишет, что хочет, в каком хочет формате и совершенно не заботится о результатах. Яркий тому пример — декодирование сигнала с Метеор-М. Обычно алгоритм получения картинки выглядит следующим образом:


    1. Записать сигнал
    2. Запустить SDR# с определёнными плагинами и настройками и демодулировать сигнал. На выходе получается бинарный файл.
    3. Запустить LRPToffLineDecoder и на вход передать бинарный файл, полученный ранее.
    4. Из LRPToffLineDecoder сохранить картинку куда-нибудь.

    Видимо, всех радиолюбителей такой подход устраивал, если за 9 лет существования спутника на орбите, ничего лучше не было придумано. Но для автоматического получения картинки такой подход не работает:


    1. Весь софт работает под Windows
    2. Нельзя запускать в headless режиме. Софт — это формочки с кнопочками.
    3. Невозможно получить обратную связь по приёму сигнала. Нет никаких метрик, по которым можно было бы оценить хорошо или плохо была принята картинка.

    Из-за этого я забросил вэб формочки кровавого энтепрайза и начал долгое погружение в гремучий мир DSP. На полное декодирование сигнала у меня ушло 2 месяца свободного времени.


    Разбор сигнала логически можно представить как две фазы:


    1. Демодуляция. Преобразование аудио-сигнала в информацию.
    2. Декодирование. Преобразование информации в понятный пользователю вид.

    Демодуляция


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


    Итак, нужно демодулировать QPSK сигнал на Java. То ли никому это неинтересно, то ли Java медленная, но я не нашёл ни одной библиотеки, которые бы это делали. Поэтому я взял существующий проект демодуляции QPSK сигнала для GNURadio и начал переписывать блоки на Java.


    QPSK demodulation


    Блоки демодуляции


    WavFile source и Float to Complex. Чтение из .wav файла IQ сигнала. Значения записаны в файле последовательно. Сначала реальная часть, потом мнимая, потом реальная и так далее. В GNURadio есть свои типы данных и каждый блок рассчин для работы только с определёнными. Так как у нас QPSK модуляция, то нам нужно использовать комплексные числа. Здесь ничего сложного, так как в Java есть поддержка .wav файлов — AudioSystem.getAudioInputStream(inputStream).


    Low Pass Filter. Это первый фильтр, который предназначен для фильтрации низких частот. Так как наш сигнал занимает 130Mhz, то нам надо отфильтровать лишние частоты.


    lowpass


    На картинке выше, частоты нашего сигнала обведены зелёным. Остальные частоты справа должны быть отфильтрованы. Это делается с помощью КИХ-фильтра. Для программиста это выглядит как:


    1. Взять последние N значений, текущее и положить в массив
    2. Перемножить получившийся массив с заранее заданным (вычисляется из ЛАФЧХ фильтра). По сути, перемножение одного вектора на другой.
    3. Все значения результирующего массива сложить.
    4. Это и будет результат.

    AGC. Автоматическая регулировка усиления.


    agc


    С точки зрения реализации — перемножение входного сигнала на некий уровень и вычисление следующего уровня в зависимости от текущего.


    Root Raised Cosine Filter. Используется для уменьшения Intersymbol interference. Если вкратце, то при получении сигнала одни уровни переданного сигнала могут накладываться на последующие. Чтобы выделить максимальный уровень сигнала между символами, применяют этот фильтр. Работает так же как и Low Pass Filter, но использует другую ЛАФЧХ


    rrcf


    Costas Loop — это алгоритм фазовой подстройки частоты. Для чего он нужен? Например, для того, чтобы корректировать доплеровское смещение частоты. Так как, мы точно знаем, что фаза у нас скачет по четырём уровням, то можно сравнивать значение двух разных фаз. Если оно отличается на дельта, то корректировать частоту. Это ярче всего видно на картинках внизу.


    before costas loop


    До коррекции частоты, у нас круг (почти). Это значит, что частота немного меняется каждый раз, и точки фазы не попадают в одно и то же место.


    after costas loop


    Здесь уже значительно лучше — видны 4 отчётливые точки фазы.


    Clock recovery MM. Этот блок пытается восстановить шаг часов передатчика и отбрасывает все сэмплы, которые не относятся к изменению уровня. Примерная схема работы изображена ниже:


    clock recovery in a nutshell


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


    clock recovery


    Constellation Soft Decoder. Этот блок преобразует координаты в вероятность битов. Тут надо остановится поподробнее, так это очень важно в дальнейшем. Допустим все точки у которых координаты положительные будут отображаться в пару "00".


    initial


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


    hard decision


    Если мы будем делать жёсткое решение, то координаты красной точки мы преобразуем в пару "00". При этом мы потеряем информацию о том, что точка-то почти "01" (в нижним квадранте). А эта информация на самом деле может помочь в декодировании. Есть алгоритмы, которые дают лучшие результаты, если передать эту информацию. Поэтому вместо того, чтобы принимать жёсткое решение, надо посчитать вероятность 0 и вероятность 1. Например 100% вероятность нахождения в правом верхнем квадранте будет 255,255. Использование мягких решений увеличивает поток данных в 8 раз, зато даёт более лучшие результаты при декодировании сигнала. Для того, чтобы посчитать мягкое решение, необходимо рассчитать расстояние между текущей точкой и каждой из точек созвездия.


    Rail, Float to char, File sink. Эти блоки немного преобразуют результаты и записывают их в файл. Для моего декодера записывать результаты в промежуточный файл не надо. Но в целях отладки было крайне полезно сначала демодулировать сигнал из Java, а затем посмотреть может ли LRPToffLineDecoder извлечь картинку.


    Декодирование


    Итак, у нас есть поток 0 и 1 из которого нужно получить картинку. Для начала необходимо открыть официальную документацию, где неплохо описана структура пакетов. Некоторые моменты там были опущены, поэтому я открыл еще более официальную документацию.


    Декодирование состоит из следующих этапов:


    • поиск в потоке бит синхромаркера. Каждый пакет начинается с него.
    • после того, как синхромаркер был найден, декодировать следующие 16384 бита алгоритмом Витерби
    • результат подвергнуть скрэмблированию, после чего
    • применить коды рида соломона и извлечь данные транспортного кадра
    • из последовательности транспортных кадров извлечь последовательность парциальных пакетов. Для тех, кто изучал модель OSI вопросов быть не должно.
    • из парциальных пакетов извлечь MCU формата JPEG
    • декодировать пикселы JPEG, используя коды Хаффмана и Run-length coding
    • правильно заполнить пикселами картинку с учётом пропущенных пакетов
    • наложить 3 канала друг на друга с учётом пропущенных пакетов

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


    compare


    К сожалению общая картина занимает 4500x2800px, поэтому я привожу сильно пожатую версию.


    Оптимизации


    А при чём тут Java? — скажут самые стойкие, кто смог дочитать до сюда.


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


    Итак, я начну с декодирования. При запуске профайлера, ничего странного не обнаружилось:


    • одно ядро работает на полную мощность
    • самый горячий метод — это декодирование Витерби. Сложность этого алгоритма О(TS), где T — это длина массива данных, S — пространство состояний. В нашем случае у нас 8 (бит) 4 (00,01,10,11) = 32.

    Помимо CPU, я запустил еще анализ аллокаций объектов. Оказывается, этот метод создаёт миллионы короткоживущих объектов. Они создают лишнюю нагрузку на GC и процессор. Посмотрим, можно ли это оптимизировать. Наивная реализация создаёт связный список для хранения решений каждого входящего бита:


        LinkedList<long[]> decisions = new LinkedList<long[]>();
        for (int i = 0; i < data.length; i += 2) {
            long[] d = new long[2]; //decision
            for (int b = 0; b < 32; b++) {
                d[b / 16] = ...;
            }
            ...
            decisions.add(d);
        }

    Это можно попробовать развернуть в одномерный массив и обращаться к результатам хитро вычисляя индекс. Например так:


        long[] decisions = new long[data.length];
        for (int i = 0; i < data.length; i += 2) {
            decisions[i + b / 16] = ...;
        }

    В итоге, на одно декодирование создаётся один объект — массив decisions. ViterbiSoft можно ещё дальше оптимизировать. Например, зная то, что размер массива всегда одинаковый, создать long[] decisions в конструкторе и переиспользовать.


    Ещё одним проблемным местом оказался класс FixedLengthTagger. Этот класс содержит скользящее окно. Оно работает следующим образом:


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

    Наивная реализация использовала LinkedList и алгоритм движения окна был такой:

        window.offerLast(curByte);
        if (window.size() > packet_len) {
            window.removeFirst();
        }

    На самом деле это очень накладная операция, так как на каждую операцию offerLast внутри LinkedList создаётся объект Node. Он будет содержать наш байт и иметь ссылки на следующий элемент списка и предыдущий. Однако, зная, что размер нашего окна фиксированный, его можно заменить на циклический массив:


        window[windowIndex] = curByte;
        windowIndex++;
        if (windowIndex >= window.length) {
            windowIndex = 0;
        }

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


        byte[] packet;
        System.arraycopy(window, windowIndex, packet, 0, window.length - windowIndex);
        System.arraycopy(window, 0, packet, window.length - windowIndex, windowIndex);

    Эти две оптимизации позволили уменьшить время декодирования файла с 4 минут до 20 секунд. Для сравнения LRPToffLineDecoder делает это за 40 секунд.


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


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


    profiling demodulation


    Тут мало что можно сделать: память не выделяется, безумных циклов нет. Единственное что интересно проверить это компилирует ли Java JIT код в SIMD инструкции. Если вкратце, то это специальные инструкции процессора, которые работают с 128битными регистрами и обрабатывают их одной командой. Схематично это выглядит так:


    simd


    Как видно, такие инструкции идеально ложаться на КИХ-фильтры. GNURadio использует библиотеку VOLK, которая в зависимости от поддерживаемой архитектуры, может использовать SIMD инструкции. В Java скорей всего вызывать JNI обёртку будет значительно затратнее, чем выигрыш от использования таких инструкций. Одна надежда на JIT, который может съоптимизировать перемножение одного массива на другой. Чтобы это проверить, необходимо запустить JVM с опцией "-XX:CompileCommand=print,*FIRFilter.filterComplex". Она заставит JVM выводить ассемблерный код для метода filterComplex из класса FIRFilter.


    Вот что у меня получилось при запуске с Oracle JDK 1.8.0_161 на MacBook Air:


    0x0000000115994750: vmovss 0x10(%r10,%r11,4),%xmm1
    0x0000000115994757: vmulss 0x10(%rcx,%r11,4),%xmm1,%xmm4
    0x000000011599475e: vmulss 0x10(%r8,%r11,4),%xmm1,%xmm1
    0x0000000115994765: vaddss %xmm2,%xmm4,%xmm3
    0x0000000115994769: vaddss %xmm0,%xmm1,%xmm0
    0x000000011599476d: movslq %r11d,%r9
    0x0000000115994770: vmovss 0x1c(%r10,%r9,4),%xmm2
    0x0000000115994777: vmulss 0x1c(%rcx,%r9,4),%xmm2,%xmm8
    0x000000011599477e: vmulss 0x1c(%r8,%r9,4),%xmm2,%xmm1
    0x0000000115994785: vmovss 0x14(%r10,%r9,4),%xmm4
    0x000000011599478c: vmulss 0x14(%rcx,%r9,4),%xmm4,%xmm2
    0x0000000115994793: vmulss 0x14(%r8,%r9,4),%xmm4,%xmm5
    0x000000011599479a: vmovss 0x18(%r10,%r9,4),%xmm4 ;*faload
    ; — ru.r2cloud.jradio.blocks.FIRFilter::filterComplex@24 (line 26)

    Судя по всему SIMD инструкции не используются.


    Ещё одним интересным местом стало вычисление sincos. Блок Costasloop вычисляет значение синуса и косинуса для одного и того же угла (фазы) для каждого входящего значения. В CPU есть специальная команда — fsincos. Она вычисляет одновременно синус и косинус угла. Однако, в Java такой функции нет. Да и реализовывать её непонятно как: надо либо возвращать double[] (а это сильно ударит по GC), либо возвращать одно значение как результат работы функции, а другое через изменяемый параметр (double в функцию Java передаётся копированием, а Double опять же ударит по GC). Наивная же имплементация выглядит так:


        float sinImg = (float) Math.sin(-d_phase);
        float cosReal = (float) Math.cos(-d_phase);

    Можно попробовать вспомнить тригонометрию и заменить на:


        float sinImg = (float) Math.sin(-d_phase);
        float cosReal = (float) Math.sqrt(1 - sinImg * sinImg) + calculate sign;

    Переход с Math.cos на Math.sqrt позволил сократить время демодуляции с 6 минут до 5 минут и 30 секунд. Тригонометрические операции можно ещё больше ускорить, если использовать таблицы поиска. Однако, я пока не готов исследовать зависимость результатов декодирования от точности таблиц. Может быть кто-нибудь поможет с этим?


    Заключение


    Поскольку я использовал одни и те же блоки для демодуляции сигнала, то можно сравнить время работы Java программы и GNURadio. Как и ожидалось, GNURadio быстрее: ~2 минуты против 5.5 минут. Да, Java почти в 2 раза медленее. Но, если учесть, что файл записывался в течении 15 минут, то производительности Java вполне хватит, чтобы в реальном времени демодулировать сигнал.

    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 35
    • +1
      А сколько знаков после запятой у угла на входе в косинус (синус)? В смысле, сколько знаков достаточно для вычислений? Прикрутить предвычисленную таблицу на миллион значений — доступ к массиву по индексу должен быть быстрее вычисления тригонометрической функции.

      Upd. Сорри, автор написал что не готов это исследовать.
      • 0

        Да, там компромисс между точностью, объёму памяти (миллион float это ~3мб), и скоростью работы.


        В идеале точность надо определять в зависимости от разрядности ADC

        • 0
          Планируется отличное от писи железо? 4 Мб под такое дело не много, джава все равно сразу сотню под себя при запуске отбирает.
          • 0

            Я запускаю это под raspberrypi на урезанной версии Java. Сейчас heap занимает ~8мб, поэтому увеличится в полтора раза. 12мб опять же немного, так что надо подумать :)

        • +1
          Сразу Doom вспоминается. Там всё это предвычислено в tables.c было.
          • 0
            Doom в другую эпоху был. Процессоры были медленными, память быстрой…
          • 0
            Прикрутить предвычисленную таблицу на миллион значений — доступ к массиву по индексу должен быть быстрее вычисления тригонометрической функции.
            Далеко не факт. Миллион значений — это уже L2. Туда уже не так быстро сходить.
            • +2
              Я набросал по-быстрому, у меня получилось, что у меня на core I5-3550, 8 Gb, jdk1.8.0_131,
              10^8 Math.sin выполнилось за 8.1 сек,
              10^8 MyMath.sin (по предрассчитанной таблице на миллион значений) выполнилось за 4.1 сек;
              Main
              public class Main {
                  public static void main(String[] args) {
                      final int TEST_SIZE = 100000000;
                      double [] testArr =initTestArr(TEST_SIZE);
              
                      System.out.println("прогрев " + MyMath.sin(Math.PI/3));
              
                      long startTime;
                      long endTime;
                      double summ=0;
                      startTime = System.currentTimeMillis();
                          for (int i=0; i<TEST_SIZE; i++){
                              summ += Math.sin(testArr[i]);
                          }
                      endTime = System.currentTimeMillis();
                      System.out.println("Math " + (endTime-startTime) + " " + summ );
              
                      float summf = 0;
                      startTime = System.currentTimeMillis();
                      for (int i=0; i<TEST_SIZE; i++){
                          summf += MyMath.sin(testArr[i]);
                      }
                      endTime = System.currentTimeMillis();
                      System.out.println("MyMath " + (endTime-startTime) + " " + summf);
              
                  }
              
                  private static double[] initTestArr(int size){
                      double[] arr = new double[size];
                      for (int i=0; i< size; i++){
                          arr[i]=Math.random()* Math.PI;
                      }
                      return arr;
                  }
              }
              


              MyMath
              public class MyMath {
                  private static final double PI_2 = Math.PI / 2;
                  private static final int SIN_TABLE_SIZE = 1000000;
                  private static final double INVERSE_SIZE = 1.0d / SIN_TABLE_SIZE;
                  private static final double INVERSE_PI_2 =  SIN_TABLE_SIZE * 2 / Math.PI;
                  private static float[] sinTable = initSinTable(SIN_TABLE_SIZE);
              
              
                  private static float[] initSinTable(int size) {
                      float[] table = new float[size];
                      double k = INVERSE_SIZE;
                      for (int i = 0; i < size; i++) {
                          double angle = PI_2 * i * k;
                          table[i] = (float) Math.sin(angle);
                      }
                      return table;
                  }
              
                  public static float sin(double angle) {
                      if (angle == 0) return 0;
                      if (angle < 0) return -sin(-angle);
              
                      angle = angle % Math.PI;
                      if (angle > PI_2) {
                          angle = Math.PI - angle;
                      }
              
                      int i = (int) (angle * INVERSE_PI_2);
                      i = (i < SIN_TABLE_SIZE ?  i : SIN_TABLE_SIZE-1);
                      return  sinTable[i];
                  }
              }
              
              

          • +2
            Автору респект за проделанную работу, самому приходилось заниматься чем-то схожим в контексте обработки звука, надо было быстро добавить небольшой кусок в большую программу на Java.
            Но, имхо, считать «прогретая» Java должна не хуже C/C++, но только если заморачиваться с предаллокацией и статическими буферами, а тогда это не сильно проще, чем на тех же C/C++ все написать.
            • 0
              По моему опыту, примерно эквивалентный код на прогретой java работает медленнее на 15-25%, и то не всегда. В данном примере (эта статья), думаю главная проблема в том, что код библиотеки уже устоявшийся и где-то оптимизированный (в отличие от ее порта). Поэтому разница такая.
              Но, в любом случае, как сказал автор — все равно хватает.
            • 0
              Вами проделана отличная работа. У меня появилось несколько вопросов. Получается, что вы сами алгоритмы синхронизации самостоятельно преобразовывали в код?
              Почему gnu radio, а не Matlab?
              • +1

                GNURadio уже много где используется. Например, SATNOGS и gqrx. А это значит, что любой кто знаком с этими системами может спокойно использовать блоки из моей библиотеки. Причём поскольку блоки бинарно совместимы, то можно брать параметры из GNURadio схем и подставлять в Java код — результат будет бинарно совместим (*)

                • 0
                  Итак, прошло 2 месяца, я разобрался в GNURadio. Я еще раз прочел вашу статью, и у меня образовались следующие вопрос: Обдумывали ли вы вариант, при котором вы демодулируете сигнал не из IQ файла, а из прямого эфира? Т.е. по сути, управляете SDR приемником?
                  • 0

                    Да, так тоже можно. Например, поднять rtl-tcp и читать из сокета.


                    Тут больше вопрос в удобстве. Если использовать raw iq файл, то можно делать анализ сигнала постфактум. Например, попробовать подстроить параметры декодера или сгенерировать спектрограмму сигнала.

              • 0
                А какой GC и с какими опциями использовали?
                • +1

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

                • +2
                  0x0000000115994757: vmulss 0x10(%rcx,%r11,4),%xmm1,%xmm4
                  0x000000011599475e: vmulss 0x10(%r8,%r11,4),%xmm1,%xmm1
                  0x0000000115994765: vaddss %xmm2,%xmm4,%xmm3
                  0x0000000115994769: vaddss %xmm0,%xmm1,%xmm0

                  Судя по всему SIMD инструкции используются.

                  Это не SIMD, а скалярные инструкции AVX.

                  vaddss — Scalar Single precision
                  vaddps — Packed Single precision (вот это уже SIMD)

                  • 0

                    Обновил. Похоже меня смутили xmm регистры. А зачем JVM делает операции на этих регистрах если используется только младшие 32 бита?

                    • 0
                      Не «зачем», а «почему». Потому что дизайнерам CPU показалось логичным, что модули, работающие с плавучкой, не будут использовать «целочисленные» регистры, потому доступа ни к каким других регистрам у них нет.
                      • 0
                        Или вы хотели спросить почему %st не используется и x87? Тут скорее надо спросить почему они вообще в 64-битном режиме поддерживаются!

                        Инженеры из AMD говорят, что они, всё-таки, позволяют использовать 80-битные числа, так что некоторый смысл в них есть… но ровно по этой же причине — они медленнее.

                        Так что их не нужно использовать никогда, когда вы не используете 80-битную плавучку… то есть практически — никогда. Intel мечтает их вообще убить, но я думаю мечты так и останутся мечтами…
                        • 0
                          Зачем? Трёхадресный код AVX проще и удобнее чем раритетный стековый FPU.

                      • 0
                        Simd всё-таки не используется, *ss это скалярные инструкции.
                        • 0
                          Деление довольно долгая операция, если важна скорость, лучше по возможности на сдвиг заменить.
                          Набросал тут 3 варианта, замена деления и чатичное раскрытие цикла ускоряет его почти в 3 раза.
                          public static void main(String[] args) {
                                  
                                  int iters = Integer.MAX_VALUE;
                                  long time = System.currentTimeMillis();
                                  long a = 0;
                                  for (int i = 0; i < iters; i++) {
                                      a += i / 16;
                                  }
                                  System.out.print((System.currentTimeMillis() - time) + "ms\n");
                          
                                  time = System.currentTimeMillis();
                                  for (int i = 0; i < iters; i++) {
                                      a += i >> 4;
                                  }
                                  System.out.print((System.currentTimeMillis() - time) + "ms\n");
                                  
                                  iters /= 4;
                                  time = System.currentTimeMillis();
                                  for (int i = 0; i < iters; i++) {
                                      a += i >> 4;
                                      a += i >> 4;
                                      a += i >> 4;
                                      a += i >> 4;
                                  }
                                  System.out.print((System.currentTimeMillis() - time) + "ms\n");
                              }
                          

                          Результат:

                          2108ms
                          1040ms
                          720ms
                          • 0

                            2 Раза запустил декодирование реальной картинки с делением. Получил:
                            43 и 36 секунд.


                            Без деления:
                            35 и 17 секунд.


                            Разброс получился большой, поэтому я добавил JMH. Получившийся тест даёт:


                            "b >> 4":
                            Benchmark Mode Cnt Score Error Units
                            ViterbiSoftBenchmark.plain thrpt 200 532.796 ± 12.010 ops/s


                            "b / 16":
                            Benchmark Mode Cnt Score Error Units
                            ViterbiSoftBenchmark.plain thrpt 200 556.942 ± 6.103 ops/s


                            То есть приближенные результаты. Есть идеи? Вот текущая версия — ViterbiSoft

                            • 0
                              Может я чего не понимаю в колбасных обрезках, но… деление на константу — это либо сдвиг, либо умножение, на худой конец. Подобные трюки компиляторы научились делать лет 20 назад.

                              Так что… чего вы, собственно, хотите тут увидеть?
                              • 0

                                yarston увидел ускорение. Самое простое это запустить код и проверить.


                                На самом деле оптимизации которые делает GCC совсем не равны оптимизациям JIT, поэтому можно ожидать чего угодно.

                                • 0
                                  Превращеним деления в умножение занимается не JIT. Этим ещё компилятор должен озаботится на этапе компиляции в байткод.

                                  Может у него особый ольдскульный компилятор, я не знаю.
                                  • 0
                                    У меня openjdk version 1.8.0_151, вышла несколько месяцев назад, никак не лет.
                                    • 0
                                      Тогда вообще неясно откуда у вас разница между сдвигами и делением. Есть ощущение, что вы неправильно «прогрели» вашу функцию или ещё как-то сглупили.

                                      Полного кода в вашем комментарии нет, так что «навскидку» сказать сложно.
                                      • 0
                                        Да, перепроверил, в прогреве дело было, идентично выходит.
                          • 0
                            Спасибо за проделанный труд и статью, очень интересно.

                            Было бы круто, если бы вы подробнее разжевали алгоритмы DSP и их реализацию на Java, так как тема эта довольно сложна для восприятия, во всяком случае, на мой вкус. Ну или хотя бы посоветовали бы литературу по этой теме.
                            • 0

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

                            • 0

                              Здравствуйте, в статье говорится о Метеоре, но ссылку на официальную документацию Вы приводите для аппарата MetOp, соответственно этапы декодирования предоставлены Вами для MetOp, да и результирующие изображения похожи больше на изображения MetOp'ов. Не возникла ли путаница между аппаратами?

                              • 0

                                Нет. МetOp это серия спутников созданных ESA. Для коммуникаций они используют протокола LRPT. Meteor-M реализовал этот протокол для передачи своих данных.


                                Именно поэтому в моём репозитории есть пакеты "lrpt" для декодирования в общем виде протокола и "meteor" для декодирования данных, специфичных для Метеора.

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

                            Самое читаемое