Что делать с производительностью Java BigDecimal? - PullRequest
58 голосов
/ 04 марта 2009

Я пишу приложения для торговли валютой, чтобы жить, поэтому мне приходится работать с денежными значениями (обидно, что у Java все еще нет десятичного типа с плавающей запятой и нет ничего, чтобы поддерживать денежные вычисления произвольной точности). "Используйте BigDecimal!" - Вы могли бы сказать. Я делаю. Но теперь у меня есть некоторый код, где производительность является проблемой, а BigDecimal более чем в 1000 раз (!) Медленнее, чем double примитивы.

Расчеты очень просты: система вычисляет a = (1/b) * c много раз (где a, b и c - значения с фиксированной точкой). Проблема, однако, заключается в этом (1/b). Я не могу использовать арифметику с фиксированной точкой, потому что нет фиксированной точки. И BigDecimal result = a.multiply(BigDecimal.ONE.divide(b).multiply(c) не только уродлив, но и вяло медленен.

Что я могу использовать для замены BigDecimal? Мне нужно как минимум 10-кратное увеличение производительности. В остальном я нашел превосходную библиотеку JScience , которая имеет арифметику произвольной точности, но она даже медленнее, чем BigDecimal.

Есть предложения?

Ответы [ 20 ]

34 голосов
/ 04 марта 2009

Может быть, вам следует начать с замены a = (1 / b) * c на a = c / b? Это не 10х, но все же что-то.

На вашем месте я бы создал собственный класс Money, в котором бы хранились длинные доллары и длинные центы, и занимался бы математикой.

14 голосов
/ 06 февраля 2013

Так что мой первоначальный ответ был просто неправильным, потому что мой тест был написан плохо. Я думаю, что я тот, кого следовало бы подвергнуть критике, а не ОП;) Возможно, это был один из первых тестов, которые я когда-либо писал ... ну, вот как вы учитесь. Вместо того, чтобы удалить ответ, вот результаты, где я не измеряю неправильную вещь. Некоторые заметки:

  • Пересчитайте массивы, чтобы я не связывался с результатами, генерируя их
  • Не никогда звоните BigDecimal.doubleValue(), так как это очень медленно
  • Не связывайтесь с результатами, добавив BigDecimal s. Просто верните одно значение и используйте оператор if, чтобы предотвратить оптимизацию компилятора. Удостоверьтесь, что он работает большую часть времени, чтобы предсказание ветвлений могло исключить эту часть кода.

Тесты:

  • BigDecimal: делайте математику в точности так, как вы ее предложили
  • BigDecNoRecip: (1 / b) * c = c / b, просто сделайте c / b
  • Двойной: сделать математику с двойными

Вот вывод:

 0% Scenario{vm=java, trial=0, benchmark=Double} 0.34 ns; ?=0.00 ns @ 3 trials
33% Scenario{vm=java, trial=0, benchmark=BigDecimal} 356.03 ns; ?=11.51 ns @ 10 trials
67% Scenario{vm=java, trial=0, benchmark=BigDecNoRecip} 301.91 ns; ?=14.86 ns @ 10 trials

    benchmark      ns linear runtime
       Double   0.335 =
   BigDecimal 356.031 ==============================
BigDecNoRecip 301.909 =========================

vm: java
trial: 0

Вот код:

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Random;

import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;

public class BigDecimalTest {
  public static class Benchmark1 extends SimpleBenchmark {
    private static int ARRAY_SIZE = 131072;

    private Random r;

    private BigDecimal[][] bigValues = new BigDecimal[3][];
    private double[][] doubleValues = new double[3][];

    @Override
    protected void setUp() throws Exception {
      super.setUp();
      r = new Random();

      for(int i = 0; i < 3; i++) {
        bigValues[i] = new BigDecimal[ARRAY_SIZE];
        doubleValues[i] = new double[ARRAY_SIZE];

        for(int j = 0; j < ARRAY_SIZE; j++) {
          doubleValues[i][j] = r.nextDouble() * 1000000;
          bigValues[i][j] = BigDecimal.valueOf(doubleValues[i][j]); 
        }
      }
    }

    public double timeDouble(int reps) {
      double returnValue = 0;
      for (int i = 0; i < reps; i++) {
        double a = doubleValues[0][reps & 131071];
        double b = doubleValues[1][reps & 131071];
        double c = doubleValues[2][reps & 131071];
        double division = a * (1/b) * c; 
        if((i & 255) == 0) returnValue = division;
      }
      return returnValue;
    }

    public BigDecimal timeBigDecimal(int reps) {
      BigDecimal returnValue = BigDecimal.ZERO;
      for (int i = 0; i < reps; i++) {
        BigDecimal a = bigValues[0][reps & 131071];
        BigDecimal b = bigValues[1][reps & 131071];
        BigDecimal c = bigValues[2][reps & 131071];
        BigDecimal division = a.multiply(BigDecimal.ONE.divide(b, MathContext.DECIMAL64).multiply(c));
        if((i & 255) == 0) returnValue = division;
      }
      return returnValue;
    }

    public BigDecimal timeBigDecNoRecip(int reps) {
      BigDecimal returnValue = BigDecimal.ZERO;
      for (int i = 0; i < reps; i++) {
        BigDecimal a = bigValues[0][reps & 131071];
        BigDecimal b = bigValues[1][reps & 131071];
        BigDecimal c = bigValues[2][reps & 131071];
        BigDecimal division = a.multiply(c.divide(b, MathContext.DECIMAL64));
        if((i & 255) == 0) returnValue = division;
      }
      return returnValue;
    }
  }

  public static void main(String... args) {
    Runner.main(Benchmark1.class, new String[0]);
  }
}
13 голосов
/ 04 марта 2009

Большинство двойных операций дают вам более чем достаточную точность. Вы можете представлять 10 триллионов долларов с точностью до цента с двойным, что может быть более чем достаточно для вас.

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

9 голосов
/ 04 марта 2009

Предполагая, что вы можете работать с некоторой произвольной, но известной точностью (скажем, миллиардной долей) и иметь известное максимальное значение, которое вам нужно обработать (триллион триллионов долларов?), Вы можете написать класс, который будет хранить это значение как целое число. миллиардов процентов. Вам понадобится два длинных, чтобы представить это. Это должно быть в десять раз медленнее, чем при использовании double; примерно в сто раз быстрее, чем BigDecimal.

Большинство операций - это просто выполнение операции над каждой частью и перенормировка. Деление немного сложнее, но не намного.

РЕДАКТИРОВАТЬ: в ответ на комментарий. Вам нужно будет реализовать операцию битового сдвига в вашем классе (легко, если множитель для длинных длинных равен степени двух). Для деления сдвиньте делитель, пока он не станет больше, чем дивиденд; вычтите сдвинутый делитель из дивиденда и увеличьте результат (с соответствующим смещением). Повторите.

ВНОВЬ РЕДАКТИРОВАТЬ: Вы можете найти, что BigInteger делает то, что вам нужно здесь.

5 голосов
/ 04 марта 2009

Возможно, вы захотите перейти к математике с фиксированной точкой. Просто ищу несколько библиотек прямо сейчас. на sourceforge с фиксированной точкой Я еще не рассматривал это подробно. beartonics

Тестировали ли вы с org.jscience.economics.money? так как это обеспечило точность. Фиксированная точка будет точно такой же, как количество битов, назначенных каждому фрагменту, но будет быстрой.

5 голосов
/ 04 марта 2009

Храните длинные как количество центов. Например, BigDecimal money = new BigDecimal ("4.20") становится long money = 420. Вам просто нужно помнить мод на 100, чтобы получить доллары и центы за вывод. Если вам нужно отследить, скажем, десятые доли цента, вместо этого оно станет long money = 4200.

3 голосов
/ 04 марта 2009

Я помню, как присутствовал на торговой презентации IBM для аппаратно-ускоренной реализации BigDecimal. Поэтому, если вашей целевой платформой является IBM System z или System p, вы можете использовать это без проблем.

Может пригодиться следующая ссылка.

http://www -03.ibm.com / серверов / включить / сайт / образование / шр / 181ee / 181ee.pdf

Обновление: ссылка больше не работает.

2 голосов
/ 29 июня 2009
Only 10x performance increase desired for something that is 1000x slower than primitive?!.

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

2 голосов
/ 04 марта 2009

Лично я не думаю, что BigDecimal идеально подходит для этого.

Вы действительно хотите реализовать свой собственный класс Money, используя longs для представления наименьшей единицы (т.е. цента, 10 цента). В этом есть некоторая работа по реализации add() и divide() и т. Д., Но это не так уж сложно.

2 голосов
/ 04 марта 2009

Какую версию JDK / JRE вы используете?

Также вы можете попробовать ArciMath BigDecimal , чтобы посмотреть, ускоряет ли это их для вас.

Edit:

Я помню, как где-то читал (я думаю, что это была Effective Java), что класс BigDecmal был изменен с JNI, вызванного на библиотеку C, на всю Java в какой-то момент ... и это стало быстрее. Возможно, любая используемая вами библиотека произвольной точности не даст вам необходимой скорости.

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