0

I have my own test class that is supposed to do timing without JVM deleting anything. Some example test times of 100,000,000 reps comparing the native that Java calls from StrictMath.sin() to my own:

30 degrees
sineNative(): 18,342,858 ns (#1), 1,574,331 ns (#10)
sinCosTanNew6(): 13,751,140 ns (#1), 1,569,848 ns (#10)

60 degrees
sineNative(): 2,520,327,020 ns (#1), 2,520,108,337 ns (#10)
sinCosTanNew6(): 12,935,959 ns (#1), 1,565,365 ns (#10)

From 30 to 60 native time skyrockets * 137 while mine is ~constant. Also, some of the times are impossibly low even when repsDone returns == reps. I expect they should be > 1*reps.

CPU: G3258 @ 4GHz
OS: Windows 7 HB SP1
Build Path: jre1.8.0_211
Reprex:

public final class MathTest {
    private static int sysReps = 1_000_000;
    private static double value = 0;
    private static final double DRAD_ANGLE_30 = 0.52359877559829887307710723054658d;
    private static final double DRAD_ANGLE_60 = 1.0471975511965977461542144610932d;
    private static double sineNative(double angle ) {
        int reps = sysReps * 100;
            //int repsDone = 0;
        value = 0;
        long startTime, endTime, timeDif;
        startTime = System.nanoTime();
        for (int index = reps - 1; index >= 0; index--) {
            value = Math.sin(angle);
                //repsDone++;
        }
        endTime = System.nanoTime();
        timeDif = endTime - startTime;
        System.out.println("sineNative(): " + timeDif + "ns for " + reps + " sine " + value + " of angle " + angle);
            //System.out.println("reps done: "+repsDone);
        return value;
    }
    private static void testSines() {
        sineNative(DRAD_ANGLE_30);
        //sinCosTanNew6(IBIT_ANGLE_30);
    }
        /* Warm Up */
    private static void repeatAll(int reps) {
        for (int index = reps - 1; index >= 0; index--) {
            testSines();
        }
    }
    public static void main(String[] args) {
        repeatAll(10);
    }
}

I tried adding angle++ in the loop and that multiplies the times to a more reasonable level, but that messes with the math. I need a way to trick it into the running all of the code all x times. Single pass times are extremely volatile and calling nanotime() takes time, so I need the average of a large number.

  • Your own implementation referenced in the question seems to be missing from the posted code. We don't know what its accuracy is, we don't know which of a dozen possible ways of computing trigonometric functions is used. I would suggest posting a minimal, complete, self-contained example code that reproduces the observations and that others can run. – njuffa May 09 '21 at 22:39
  • I changed the posted code to be specifically for the native test. Mine is not important here. How the native method works is the issue. – Caley McKibbin May 09 '21 at 23:05
  • I don't think you're measuring what you think you're measuring. https://www.baeldung.com/java-microbenchmark-harness – Matt Timmermans May 09 '21 at 23:33
  • 1
    Saying you are using Eclipse is not much use. Eclipse is an IDE; it is a tool for compiling, debugging, and running things, and other tasks for developing software. More relevant is the actual Java engine. I am not familiar with Java implementations, but they may have their own math routine implementations or use host system implementations. So you would have to identify what Java implementation you are using and hope somebody familiar with it sees this question. – Eric Postpischil May 10 '21 at 00:05
  • 1
    Looking at your times, say 5,485,984 ns for 100,000,000 repetitions of the sine of 1 degree, that is 5,485,984/1e8 = .05485984 ns per sine. On a 4 GHz processor, that is .22 CPU cycles per sine. Do you think that is realistic or there is something wrong in your measurements? – Eric Postpischil May 10 '21 at 00:08
  • First question is were you getting the right answers? What did you get for `sin(30)` for example? – user207421 May 10 '21 at 00:21
  • @EricPostpischil Java uses a native keyword to delegate such operations in the StrictMath class. However, I don't know what it is delegating to. That is one of the things I would like to know so I can find the source code for it. Regarding the cycles per sine, my cpu is exactly 4ghz on Intel G3258. So obviously if that calculation is correct something is wrong with the measurement. But how the angle could change it is a mystery to me. In general one would guess that the JIT is stripping operations if the times are too low. – Caley McKibbin May 10 '21 at 00:56
  • But the 0.22 cpu cycle per sine seems impossible even if it is reduced to cycles per instruction. Unless the cpu is able to parallelize the instructions per tick that much. I don't know enough about cpu architecture to answer that. I can't explain this time result. – Caley McKibbin May 10 '21 at 03:12
  • Edit the question to provide a [mre], including completely identifying the operating system and Java implementation and showing the specific numeric values of the arguments you pass to `sin`. – Eric Postpischil May 10 '21 at 05:45
  • You do know that `sin(30) == 0.5`, `sin(45) == sqrt(2)`, and `sin(90) == 1`? and that these special cases don't need any FP instructions? – user207421 May 10 '21 at 05:54
  • @EricPostpischil I changed the entire post. – Caley McKibbin May 11 '21 at 00:53
  • If you're trying to microbenchmark **any** code you should really link the article linked above **and** [this SO question](https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java). **tl;dr** use an existing Microbenchmark framework and read their documentation. There's *so much* that can go wrong in a Microbenchmark that you're unlikely to catch all of it if you try it on your own. – Joachim Sauer May 21 '21 at 06:37
  • I read about the JMH in a few places, but there is no claim in any documentation that it definitely solves this problem. An Oracle article on benchmark architecture says at best that is "unlikely" to do constant folding, but it does not explain exactly how and I have not succeeded in trying to apply its vague suggestion. So I frankly doubt it is possible. https://www.oracle.com/technical-resources/articles/java/architect-benchmarking.html – Caley McKibbin Jun 03 '21 at 18:10

1 Answers1

1

The problem is that you never use/refer to the results returned by sineNative. The JIT compiler is clever enough to work out that you never use the return value, so it will just do nothing eventually. A very simple way to fix this is to add a dummy check for your return value. (e.g. if (Math.sin(angle) > 1) { System.out.println("Impossible!"); })

If you are writing benchmark like this it would be useful to use something like JMH (https://github.com/openjdk/jmh) which would automatically create a blackhole for your return variable, so that the JIT compiler will not optimise the value. (see example https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_09_Blackholes.java)