Proste operacje matematyczne, z danymi w postaci Stringow od usera -> chodzi mi o wydajnosc

0

Niby banalna sprawa, ale moze ktos doradzi cos o czym nie pomyslalem.

Robie profiling aplikacji i okazuje sie ze sporo CPU zostaje zjedzone w metodzie gdzie pobiera sie dane wejsciowe jako Stringi i robi banalne operacje matematyczne (naglowek funkcji musi zostac). Refaktoring tez raczej odpada bo tam nie ma unit testow. Za to moge zrobic praktycznie wszystko z ponizsza metoda.

aha, oczywiscie dane ktore przychodza w wywolaniu moga byc (i w 70% sa) niepoprawne.

Poradzcie jak to zrobic zeby bylo ladnie i szybko dzialalo.
Swoja droga sa jakies gotowe i sprawdzone mechanizmy w Javie do tego typu rzeczy?
Java 7 (ale chetnie sie dowiem czy nowsza Java moze tu cos zmienic).

   /**
     * Performs a simple math 
     * @param operator add, subtract, divide, multiply or modulo
     * @param num1 First operand.
     * @param num2 Second operand.
     * @param percent Treat second operand as a percentage?
     * @return Output of maths operation as a string.
     */
    public static String doMath(String operator, String num1, String num2, String percent) {

         <Straszny, niewydajny kod >
         Return result;
    }
0
  1. Napisz load test - najlepiej przy pomocy JMH. Dobrze jakby jakoś reprezentował rozkład danych z produkcji (niepoprawne vs poprawne itp.).
    1a mierz
  2. Rozbij ciało na mniejsze metody - walidacja i poszczególne operacje (jeśli to ma sens).
    2a mierz
    2b odpal -XX:+PrintCompilation
    2c. profilui pod obciążeniem
  3. Wyciągaj hipotezy
    3a modyfikuj kod
    3b mierz
    3c if niezadowolony goto 3.

Strzelam w ciemno, że problemem mogą być tonami lecące exceptiony. Co z tym zrobić nie wiem. Może walidacja za pomocą regexp jest rozwiązaniem.
(ale piekło jest wybrukowane domysłami co zjada CPU).
Pamiętaj, że profilery łgają jak szalone. Zmiana pokrytych pomiarami pakietów (instrumentation filters) i klas może całkiem zmienić wyniki.

W sumie jeśli to operacja taka jak na rysunku to chyba możesz podzielić się kodem - ciekawa sprawa.

0

Z ciekawości napisałem taki naiwny benchmark:

public class AddBenchmark {


    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(AddBenchmark.class.getSimpleName())
                .build();

        new Runner(opt).run();
    }

    @Benchmark
    @Warmup(iterations = LOOP, time = TIME, timeUnit = TimeUnit.MILLISECONDS)
    @Measurement(iterations = LOOP, time = TIME, timeUnit = TimeUnit.MILLISECONDS)
    @Fork(LOOP)
    public void doMathBenchmark(Blackhole blackhole) {
        String sum = "0";
        for (int i = 0; i < MAX; i++) {

            String operator = getOperator(i % 4);
            sum = doMath(operator, sum, String.valueOf(i), "");

        }
        blackhole.consume(sum);
    }

    @Benchmark
    @Warmup(iterations = LOOP, time = TIME, timeUnit = TimeUnit.MILLISECONDS)
    @Measurement(iterations = LOOP, time = TIME, timeUnit = TimeUnit.MILLISECONDS)
    @Fork(LOOP)
    public void doMathWithExBenchmark(Blackhole blackhole) {
        String sum = "0";
        for (int i = 0; i < MAX; i++) {

            String operator = getOperator(i);

            try {
                sum = doMath(operator, sum, String.valueOf(i), "");
            } catch (Exception e) {

            }
        }
        blackhole.consume(sum);
    }

    private String getOperator(int i) {
        int i1 = i % 5;
        switch (i1) {
            case 0:
                return "+";
            case 1:
                return "-";
            case 2:
                return "/";
            case 3:
                return "*";
            case 4:
                return "asd";
            default:
                return "+";
        }
    }

    private static String doMath(String operator, String num1, String num2, String percent) {
        BigDecimal bigDecimal1 = new BigDecimal(num1);
        BigDecimal bigDecimal2 = new BigDecimal(num2);

        switch (operator) {
            case "+":
                return String.valueOf(bigDecimal1.add(bigDecimal2));
            case "-":
                return String.valueOf(bigDecimal1.subtract(bigDecimal2));
            case "/":
                return String.valueOf(bigDecimal1.divide(bigDecimal2, HALF_UP));
            case "*":
                return String.valueOf(bigDecimal1.multiply(bigDecimal2));
        }
        throw new IllegalArgumentException();
    }
}

Wyniki:

Benchmark                            Mode  Cnt   Score   Error  Units
AddBenchmark.doMathBenchmark        thrpt   25  96.035 ± 7.915  ops/s
AddBenchmark.doMathWithExBenchmark  thrpt   25  27.649 ± 0.694  ops/s
0

Skoro masz straszny, niewydajny kod, to popraw go na ładny wydajny. Czego nie zrozumiałem?

0

To w sumie powinno to WTF isc. Metoda zawiera w srodku tez "tzw. quick fix" ktory robi po prostu konkatenacje stringow i okazuje sie ze w 80% przypadkow metoda jak sama jej nazwa wskazuje laczy stringi :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1