★ wanayoo — archive 1999 https://github.com/ev3dev-lang-java/ev3dev-lang-java/issues/748Nouvelle recherche | Portail wanayoo
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance too slow for control feedback for a moving robot #748

Closed
dwalend opened this issue Nov 30, 2020 · 283 comments
Closed

Performance too slow for control feedback for a moving robot #748

dwalend opened this issue Nov 30, 2020 · 283 comments

Comments

@dwalend
Copy link
Contributor

@dwalend dwalend commented Nov 30, 2020

Steps:

  • Review alternatives to improve performance in Sysfs.java
  • Evaluate the performance with JMH
  • Consolidate changes with current architecture

Original Request:

The java code attached runs in about 100 to 140 milliseconds per iteration. Similar code using the ev3 classroom from LEGO runs each iteration in 2 to 4 milliseconds. 100 milliseconds is enough time for the robot to travel an inch or 3, which becomes significant quickly.

My question is - is the delay in the ev3 os streaming implementation, or in ev3dev-lang-java:2.6.2-SNAPSHOT code that reads and writes those streams? That'll tell me what to try first to speed it up my own code. If it's at the OS level then probably the best I can do is read and write the buffers in parallel. If it's in Java IO then maybe a JNI approach or an NIO loop could help.

I'm using ev3dev-lang-java:2.6.2-SNAPSHOT - on a stock EV3, not overclocked, with the stock ev3dev OS SDD card. It does touch the lejos SampleProvider to use the gyro sensor, but that seems to pass right through to Sysfs.readFloat(...).

Juan Antonio Breña Moral asked me to open an issue for the ev3dev-lang-java project here.

@dwalend
Copy link
Contributor Author

@dwalend dwalend commented Nov 30, 2020

Here's the java code:

package ev3.timing;

import ev3dev.actuators.ev3.EV3Led;
import ev3dev.actuators.ev3.EV3Led.Direction;
import ev3dev.actuators.lego.motors.EV3LargeRegulatedMotor;
import ev3dev.sensors.EV3Key;
import ev3dev.sensors.ev3.EV3GyroSensor;
import lejos.hardware.port.MotorPort;
import lejos.hardware.port.SensorPort;
import lejos.robotics.SampleProvider;

public class TimingTest {

    //equivalent loop on just ev3 classroom is .3.6 / 100 = 0.0036 per iteration = 3.6 milliseconds - so it's a factor of about 100x
    public static void main(String[] args) {
        EV3LargeRegulatedMotor leftMotor = new EV3LargeRegulatedMotor(MotorPort.B);
        EV3LargeRegulatedMotor rightMotor = new EV3LargeRegulatedMotor(MotorPort.C);
        System.out.println("motors created");

        EV3GyroSensor gyroSensor = new EV3GyroSensor(SensorPort.S2);
        SampleProvider gyroSampleProvider = gyroSensor.getAngleMode();
        float[] gyroArray = new float[gyroSensor.sampleSize()];
        System.out.println("gyro created");

        warmUp(gyroSampleProvider,leftMotor,rightMotor);

        long[] timingResults = new long[10];
        for(int i = 0 ; i < timingResults.length ; i++) {
            long startTime = System.currentTimeMillis();

            //readHeading
            gyroSampleProvider.fetchSample(gyroArray, 0);
            float heading = gyroArray[0];
            //read each motor's odometer
            int leftOdometer = leftMotor.getTachoCount();
            int rightOdometer = rightMotor.getTachoCount();
            //set each motor's speed
            leftMotor.setSpeed(0);
            rightMotor.setSpeed(0);

            long endTime = System.currentTimeMillis();
            timingResults[i] = endTime - startTime;
            //todo set the array value

        }
        long total = 0 ;
        for(long r : timingResults ) {
            System.out.println(r);
            total = total + r;
        }
        System.out.println("average is "+(total/timingResults.length))   ;
    }

    static void warmUp(SampleProvider gyroSampleProvider,EV3LargeRegulatedMotor leftMotor,EV3LargeRegulatedMotor rightMotor) {
        long startTime = System.currentTimeMillis();

        //readHeading
        float[] gyroArray = new float[1];
        gyroSampleProvider.fetchSample(gyroArray, 0);
        //read each motor's odometer
        int leftOdometer = leftMotor.getTachoCount();
        int rightOdometer = rightMotor.getTachoCount();
        //set each motor's speed
        leftMotor.setSpeed(0);
        rightMotor.setSpeed(0);

        long endTime = System.currentTimeMillis();
    }
}
@dwalend
Copy link
Contributor Author

@dwalend dwalend commented Nov 30, 2020

Here's the ev3 classroom code that runs in 2-4 milliseconds per iteration.

image

@jabrena jabrena self-assigned this Dec 1, 2020
@jabrena jabrena added this to the v1.0.0 milestone Dec 1, 2020
@jabrena
Copy link
Member

@jabrena jabrena commented Dec 1, 2020

Hi @dwalend,

many thanks for the issue.
I will review this week, using the example provided.

Juan Antonio

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 1, 2020

  • Review imports
  • Create a benchmark with multiple implementations

https://github.com/ev3dev-lang-java/ev3dev-lang-java/blob/master/src/main/java/ev3dev/utils/Sysfs.java
https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#readAllLines(java.nio.file.Path,%20java.nio.charset.Charset)

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

I will try to hack something together in C++ in the coming days to see what is the limit of the underlying OS.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

I have an initial version of the C++ benchmark available.

"Rolling-release" links (pointing at master, not the commit https://github.com/JakubVanek/ev3dev-lang-cpp-bench/commit/b01a3b22d6d4bbc3cecd23c53c214706d4e23e76):

However, I've noticed that even though the benchmark sets the speed attribute, it doesn't order the motors to start moving. Is this intentional? I've followed this in the C++ program. I believe the situation with leJOS/ev3dev-lang-java is similar and you'd have to call forward() for the speed setting to take full effect.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

-[ ] Review imports
-[ ] Create a benchmark with multiple implementations

@jabrena I'd check the ideas proposed in #652, while not everything is useful, the writeString method looks more optimized than the current one.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

It seems that the compiler in Ubuntu 18.04 is too new to compile for ev3dev-stretch, I'll provide a binary built with a "universal" compiler (download here) that I was using for different C development.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

EDIT: I'm going to use a arm-linux-musleabi-cross compiler from http://musl.cc/, they're available for Windows as well, so then a comparable setup should be available.

@dwalend
Copy link
Contributor Author

@dwalend dwalend commented Dec 1, 2020

I've noticed that even though the benchmark sets the speed attribute, it doesn't order the motors to start moving. Is this intentional?

That's correct. Starting the motors isn't in the control loop, so I left it out of the benchmark. That gives me a reason better than "I don't want to take the wheels off the robot, and don't want it crashing into stuff on my desk."

@dwalend
Copy link
Contributor Author

@dwalend dwalend commented Dec 1, 2020

EDIT: I'm going to use a arm-linux-musleabi-cross compiler from http://musl.cc/, they're available for Windows as well, so then a comparable setup should be available.

If that doesn't work out - ev3dev has a Docker recipe for a stretch dev system .

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

EDIT: I'm going to use a arm-linux-musleabi-cross compiler from http://musl.cc/, they're available for Windows as well, so then a comparable setup should be available.

If that doesn't work out - ev3dev has a Docker recipe for a solid dev environment.

Hmm, I agree, that would be more convenient if that is an option.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

Results for the musl version:

robot@ev3dev:~$ ./cxxbench 
motors created
gyro created
3392
785
3280
3283
2666
3552
1109
4874
847
3151
average is 2.6939 ms

and this is one of the worse runs, typically it's around 1.4 ms. The mid-results are off, I've switched the code to microseconds in C++, while in Java they're milliseconds.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

I've noticed that even though the benchmark sets the speed attribute, it doesn't order the motors to start moving. Is this intentional?

That's correct. Starting the motors isn't in the control loop, so I left it out of the benchmark. That gives me a reason better than "I don't want to take the wheels off the robot, and don't want it crashing into stuff on my desk."

I think this will not work in the production code -- AFAIK the speed change is committed only after a forward() call is made in leJOS / run-forever command in invoked in ev3dev.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

Results for the docker-compiled version:

robot@ev3dev:~$ ./cxxbench 
motors created
gyro created
1608
1832
1503
2751
1660
1505
2033
1725
2797
1657
average is 1.9071 ms

Quickly looking at a few runs, the mean is about 1.9 ms.

This is with run-forever added to my latest version of the benchmark, in Java, there needs to be something like leftMotor.forward(); rightMotor.forward(); (alt. with synchronization start+end).

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

If it's in Java IO then maybe a JNI approach or an NIO loop could help.

Writing the control loop in C/C++ and then communicating with non-critical logic in Java would most certainly work, although the impact of JIT/GC running next to it would have to be measured. On the other hand, if the logic runs completely in its own thread, it could switch itself to realtime SCHED_RR/SCHED_FIFO scheduling. With that, whatever is running next to it shouldn't present a problem.

Using JNI to do just the IO (i.e. replacing Sysfs class with a JNI-backed implementation) is another way to go. This would preserve the existing outer API, but the performance might be slightly lower.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

Results of the last C++ version with SCHED_RR scheduling enabled:

robot@ev3dev:~$ sudo chrt -r 50 ./cxxbench 
motors created
gyro created
1468
1548
1306
1168
1506
1460
1281
1186
1599
1299
average is 1.3821 ms

The variance now looks much lower too.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 1, 2020

I think the C/C++ version could be hand-optimized to be even faster, my current goal was to make look the same as the Java benchmark. However, the results should have the same order-of-magnitude. There will be the same context switches etc. (= limitation of ev3dev), just the UTF-8 conversion and similar stuff could be cut out.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 2, 2020

Hmm, to be fair to the Java version, proper warmup for microbenchmarking should have much more iterations than one. That being said, the same warmup would then have to be done in the production code too.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 2, 2020

I tried to rewrite the program using the ugliest hacks I know and this is what I got (binary, source):

robot@ev3dev:~$ sudo chrt -r 50 ./overkill 
motors created
gyro created
454
292
204
276
243
227
171
173
167
255
average is 0.246200 ms

I'd consider that nearing the limit of what is achievable with ev3dev. There are still some places that could be optimized (sprintf for int->string conversion isn't optimal), but something of that style would have to be used anyway for it to be useful.

@dwalend
Copy link
Contributor Author

@dwalend dwalend commented Dec 2, 2020

I tried to rewrite the program using the ugliest hacks I know and this is what I got ([binary]

Up until this one the speeds you saw in C++ lined up with the LEGO classroom scratch. (And thanks for the reminder of why I stopped doing C++. ... You could put assembly code in that for more ugly, but I don't know that you can gain anything.)

The warm-up is short for Java - hotspot normally kicks in after 8 iterations. However, the biggest time-sink on the ev3 seems to be that initial loading of classes. I didn't see much drop-off in longer tests, and - like you mentioned - it's not terribly predictable.

It looks like there's two orders of magnitude potential gain from the base case in Java to reasonable C code.

@dwalend
Copy link
Contributor Author

@dwalend dwalend commented Dec 2, 2020

I want to pick on this method in Sysfs.java as a first step:

public static String readString(final String filePath) {
		if(log.isTraceEnabled())
			log.trace("cat " + filePath);
		try {
			final Path path = Paths.get(filePath);
			if(existFile(path) && Files.isReadable(path)){
				final String result = Files.readAllLines(path, Charset.forName("UTF-8")).get(0);
				if(log.isTraceEnabled())
					log.trace("value: {}", result);
				return result;
			}
			throw new IOException("Problem reading path: " + filePath);
		} catch (IOException e) {
			log.error(e.getLocalizedMessage(), e);
			throw new RuntimeException("Problem reading path: " + filePath, e);
		}
	}

It's opening and closing the file every time. Would it be possible - as part of initializing the sensor - to do the safety checks at init time, and keep the file open ? (And leave it up to the client code to close when done using the sensor?) That'd save all the IO set-up, and almost all of the memory allocation.

The trade is that the sensor would pick up some more - and more complicated state.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 2, 2020

I'd consider that nearing the limit of what is achievable with ev3dev. There are still some places that could be optimized (sprintf for int->string conversion isn't optimal), but something of that style would have to be used anyway for it to be useful.

It seems I was wrong again. By removing int<->string conversion altogether, the time is nearly halved:

robot@ev3dev:~$ sudo chrt -r 50 ./overkill2 
motors created
gyro created
142
121
110
186
125
118
107
110
107
112
average is 0.123800 ms
@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 2, 2020

Assembly would indeed be the next step, but this seems like too much work.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 2, 2020

Up until this one the speeds you saw in C++ lined up with the LEGO classroom scratch. (And thanks for the reminder of why I stopped doing C++. ... You could put assembly code in that for more ugly, but I don't know that you can gain anything.)

I have to agree, from this viewpoint the C++ result is a bit of an embarrassment. On the other hand, if this Scratch variant is based on Microsoft MakeCode, I think they might be generating ARMv4T code ahead of time and that could explain their great performance. But if this is based on the stock firmware's VM, then it seems engineers at LEGO optimized it very well.

The warm-up is short for Java - hotspot normally kicks in after 8 iterations. However, the biggest time-sink on the ev3 seems to be that initial loading of classes. I didn't see much drop-off in longer tests, and - like you mentioned - it's not terribly predictable.

In that case this warmup looks reasonable, I was thinking the threshold was in the order of 1000s of calls. I can confirm I remember classloading sometimes took a lot of time.

@jabrena jabrena reopened this Dec 18, 2020
@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 18, 2020

@jabrena I have read those and I still don't follow why the solution as-is isn't thread safe. (Yes, its performance is certainly not optimal, but that does not fall under thread safety).

Please read more about Thread Safety:

This does list the immutable nature, but it also acknowledges locking. If all public entrypoints that access the data are synchronized on the same object, then IIUC all accesses are mutually excluded. Correct me if I'm wrong in this.

Resource starvation might still occur if multiple threads are reading from a file on a multicore system. This seems to fall to performance instead of thread safety though.

Similar. The one valid problem that it lists is that an attacker can lock the static Sysfs monitor and deny all read/write operations this way. Moving the synchronized to an internal lock would fix that.

This one has the most information. It does not directly address locking. One of the recommendations is "Avoid Global Variables", but that is for "Strategy 1: Confinement". But this is not the only strategy available.

The entire next reading is dedicated to locking: http://web.mit.edu/6.031/www/fa17/classes/21-locks/ To paraphrase the reading:

// Thread safety argument:
//   all accesses to the cached variables happen within Sysfs methods,
//   which are all guarded by Sysfs's static lock.

For deadlocks, a similar argument could be made - the Sysfs lock will always be the last lock to be acquired. Sysfs execution doesn't block on other locks. It does block on system I/O, so writing to a UNIX pipe with Sysfs would break. AFAIK /sys contains no pipes, but it is a potential hazard.

I'm intentionally not touching lockless shared structures or creating own low-level locking primitives here because I know these are genuinely hard.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 18, 2020

I would again appreciate technical counter-arguments, while I learnt new stuff when reading the materials, I still don't see why the code isn't thread safe (yes, performance is going to be suboptimal).

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 18, 2020

Sorry, I am not going to invest more time in this discussion.

I will continue with the process this weekend.

Kind reminder: try to not flood this issue with any comment that you have in your mind. In other case, you will be blocked.
Read the document: https://github.com/ev3dev-lang-java/ev3dev-lang-java/blob/master/CODE_OF_CONDUCT.md to understand your bad behavour.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 18, 2020

OK, I will try to take a step back. It's true that this argument does not help to resolve the overall issue.

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 18, 2020

@JakubVanek I am talking about your rude behavior, not about arguments. You choose. I am clear.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 18, 2020

Yes, I acknowledge that I behaved rudely and I will try to adjust my future behaviour not to create conflict.

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 20, 2020

Testing GitKraken, a visual tool to remove non required changes in the branch sysfs_perf
https://www.gitkraken.com/

@dwalend
Copy link
Contributor Author

@dwalend dwalend commented Dec 22, 2020

@jabrena - how goes the testing?

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 22, 2020

Hi @dwalend,

Tomorrow, I will show some results :)

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 23, 2020

Status:

  • JMH with GC Info & Stack for 10 measures (0.30h) Mode.AverageTime Unit Test
  • JMH with GC Info & Stack for 10 measures (0.30h) Mode.Throughput Unit Test
  • JMH with GC Info & Stack for 10 measures (0.30h) Mode.SingleShotTime Unit Test
  • JMH with GC Info & Stack for 10 measures (0.30h) Mode.AverageTime Unit Test Logs Disabled
  • JMH with GC Info & Stack for 10 measures (0.30h) Mode.Throughput Unit Test Logs Disabled
  • JMH with GC Info & Stack for 10 measures (0.30h) Mode.SingleShotTime Unit Test Logs Disabled
  • JMH for 10 measures (0.30h) Mode.All TimingTest SysFs Logs Disabled
  • JMH for 10 measures (0.30h) Mode.All TimingTest SysFs2 Logs Disabled

https://jmh.morethan.io/

scp robot@192.168.1.157:/home/robot/jmh-results.json .

Tomorrow, I will upload the json to have the discussion.

I configured the JMH to not have a Warmup in order to simulate the scenario for a new Java execution.

        Options options = new OptionsBuilder()
                .include(Sysfs_ExistFile_Benchmark.class.getSimpleName())
                .include(Sysfs_writeString_Benchmark.class.getSimpleName())
                .include(Sysfs_writeInteger_Benchmark.class.getSimpleName())
                .include(Sysfs_readString_Benchmark.class.getSimpleName())
                .include(Sysfs_getElements_Benchmark.class.getSimpleName())
                .include(Sysfs_existPath_Benchmark.class.getSimpleName())

                .resultFormat(ResultFormatType.JSON)
                .result("/home/robot/jmh-results.json")
                .verbosity(VerboseMode.EXTRA)
                .mode(Mode.Throughput)
                .timeUnit(TimeUnit.MILLISECONDS)
                .warmupTime(TimeValue.seconds(0))
                .measurementTime(TimeValue.milliseconds(1))
                .measurementIterations(10)
                .threads(Runtime.getRuntime().availableProcessors())
                .warmupIterations(1)
                .shouldFailOnError(false)
                .shouldDoGC(true)
                .forks(2)
                .jvmArgs("-Xmx64m", "-Xms64m", "-XX:+UseSerialGC", "-noverify", "-XX:TieredStopAtLevel=1")
                //.addProfiler(StackProfiler.class)
                //.addProfiler(GCProfiler.class)
                //.addProfiler(LinuxPerfProfiler.class)
                //.addProfiler(ClassloaderProfiler.class)
                //.addProfiler(CompilerProfiler.class)
                //.addProfiler(JmhFlightRecorderProfiler.class)
                .build();

To disable the logs:

org.slf4j.simpleLogger.defaultLogLevel=off
@jabrena
Copy link
Member

@jabrena jabrena commented Dec 25, 2020

Results using Sysfs & Sysfs2 in an aggregated test:

Original Sysfs.java
image

Sysfs2.java
image

It is clear that in average, the new implementation for method: readString

This week, I will replace current method with the new one and I will create a new issue to analyze how to increase the performance at program level. For maximum performance level, disable logs: org.slf4j.simpleLogger.defaultLogLevel=off

Some ideas for the next iteration:

  • Review class hierarchy to instantiate Sysfs
  • Review how to mock Sysfs to improve the whole project

@dwalend @JakubVanek any other idea?

Merry christmas

Juan Antonio

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 25, 2020

I have some comments related to the JMH configuration:

                .warmupTime(TimeValue.seconds(0))
                .warmupIterations(1)

I'm not sure how this exactly behaves. For SingleShot, there was one warmup iteration. For AverageTime/Throughput, there might be one too (JMH runs warmupIterations loops, each loop runs for warmupTime milliseconds; so it may have tried to run the function once to find that the time allocated had expired).

                .measurementTime(TimeValue.milliseconds(1))
                .measurementIterations(10)

Given that the operation itself takes more than a millisecond, I think that AverageTime/Throughput got effectively transformed into SingleShot. As above, JMH should do measurementIterations runs of a loop that is let to run for measurementTime ms. This is likely to hide the time counting overheads by calculating an average over multiple inner function calls (the output is that for example iteration 3 managed to do 123 calls in 2,0 seconds).

                .forks(2)

This looks good.

                .jvmArgs("-Xmx64m", "-Xms64m", "-XX:+UseSerialGC", "-noverify", "-XX:TieredStopAtLevel=1")

This does not entirely disable JIT though, this is why I was pushing -Xint earlier. I also started using -Xmx32m as there is definitely not 64 MiB of free RAM, but on the other hand this shouldn't be causing much issues.

                //.addProfiler(StackProfiler.class)
                //.addProfiler(GCProfiler.class)

Yes, this is a good idea, the last time these profilers didn't work.

@ev3dev-lang-java ev3dev-lang-java deleted a comment from JakubVanek Dec 25, 2020
@jabrena
Copy link
Member

@jabrena jabrena commented Dec 25, 2020

The profilers works. Any other idea?

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 25, 2020

The profilers works. Any other idea?

The performance measurement itself does work, but the last time there were only 0.0 and NaN values in the profiler results.

"secondaryMetrics" : {
"·gc.alloc.rate" : {
"score" : "NaN",
"scoreError" : "NaN",
"scoreConfidence" : [
"NaN",
"NaN"
],
"scorePercentiles" : {
"0.0" : "NaN",
"50.0" : "NaN",
"90.0" : "NaN",
"95.0" : "NaN",
"99.0" : "NaN",
"99.9" : "NaN",
"99.99" : "NaN",
"99.999" : "NaN",
"99.9999" : "NaN",
"100.0" : "NaN"
},
"scoreUnit" : "MB/sec",
"rawData" : [
[
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN"
],
[
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN"
]
]
},
"·gc.count" : {
"score" : 0.0,
"scoreError" : "NaN",
"scoreConfidence" : [
0.0,
0.0
],
"scorePercentiles" : {
"0.0" : 0.0,
"50.0" : 0.0,
"90.0" : 0.0,
"95.0" : 0.0,
"99.0" : 0.0,
"99.9" : 0.0,
"99.99" : 0.0,
"99.999" : 0.0,
"99.9999" : 0.0,
"100.0" : 0.0
},
"scoreUnit" : "counts",
"rawData" : [
[
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
[
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
]
},
"·stack" : {
"score" : "NaN",
"scoreError" : "NaN",
"scoreConfidence" : [
"NaN",
"NaN"
],
"scorePercentiles" : {
"0.0" : "NaN",
"50.0" : "NaN",
"90.0" : "NaN",
"95.0" : "NaN",
"99.0" : "NaN",
"99.9" : "NaN",
"99.99" : "NaN",
"99.999" : "NaN",
"99.9999" : "NaN",
"100.0" : "NaN"
},
"scoreUnit" : "---",
"rawData" : [
[
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN"
],
[
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN",
"NaN"
]
]
}
}
},

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 25, 2020

Hi @JakubVanek,

this is a kind a result from JMH and indeed, I didn´t add to reduce the time invested in the tests but they works.
It is a pity that you don´t believe in JMH but this is the official Java tool for MicroBenchmarking.

Review in Internet, you will find positive results with JMH.

@JakubVanek
Copy link
Contributor

@JakubVanek JakubVanek commented Dec 25, 2020

Hi @JakubVanek,

this is a kind a result from JMH and indeed, I didn´t add to reduce the time invested in the tests but they works.
It is a pity that you don´t believe in JMH but this is the official Java tool for MicroBenchmarking.

Review in Internet, you will find positive results with JMH.

I do admit that my initial opinions about JMH based on the first measured data in this thread were wrong. My evaluation was coming from the error margins; I thought this was the best JMH was capable of on EV3.

However, after experiments, I found that JMH is capable of providing solid results even on the EV3 (this was done in parallel with the RtControlBench tests, see earlier comments). However, it seems that to get small error margins from JMH, I had to keep some things in mind (this is not specific to JMH though, any measurement of anything would be affected by this).

  • steady state need to be ensured (either the JIT has to be warmed up, or it has to be disabled altogether, or sufficiently small amount of measurements has to be done (but see below))
  • the more measurements (iteration count or measurementTime), the better
  • because JMH runs two JVMs in parallel (can be verified by running top when the benchmark is running), care needs to be taken to prevent a situation where one of the JVM's memory has to be pushed out to swap
  • presence of sensors in sensor ports can affect the resulting score due to the additional CPU load they create

I'm also not saying that the stack profiler and GC profiler are broken in general, I just want to point out that from the data that I can access, it seems it didn't produce interpretable results in this specific environment.

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 25, 2020

Oki

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 27, 2020

@dwalend Any feedback?

@dwalend
Copy link
Contributor Author

@dwalend dwalend commented Dec 27, 2020

I'm pretty pragmatic about how performance is measured. Whatever you like is fine, so long as it matches with the reality of how fast my kid can close a control loop. I saw useful values via System.currentTimeMillis().

If the JMH profiler is available as a library you can probably run the profiler in the same JVM as the code you are observing to save the switching overhead. I'm not sure if that is of interest.

And the same for NIO vs JNA vs JNI. Whatever performs best is the right answer.

I like the advantage of an all-Java NIO fix because it takes me a lot less to explain what the code does to the kids I'm teaching. "This code reads the sensor the same way it would read a file. It's tweaked out a bit to read the file really fast," is pretty easy. In fact "It reads this many bytes or fewer," is helpful for inviting the kids a level deeper without a huge context switch.

The next big step forward in performance is keeping the file handle open, at least on the NIO front. It's a big boost for not a lot of new concepts or complexity. Closable is extremely common, inescapable, and other people do a great job of explaining it.

As for what API to supply - KISS is the best answer. Java's culture and community tends to bring in a lot of object-oriented hierarchy complexity that doesn't help a developer understand or use a library. Here on the EV3 it really matters. Restarts are frequent. Loading extra classes is really slow. Each extra layer of abstraction - especially classes - has a big cost, but not a lot of benefit. The kid plugged in the gyro sensor to read the robot's heading. The fact that it has the same API as the color sensor doesn't make up for the complexity of creating a float array to get one number - a number that is always an int. Discover the actual useful commonality and refactor to take advantage of it instead of forcing the internal programming model on people who use your library. Do a good job of exposing everything using a similar pattern, but do a great job with the simple common use cases.

I especially want to discourage anything that involves making decisions about concurrency. The big lesson we learned while writing and proofing Concurrency in Practice was that concurrency was a really hard systemic problem best solved in outermost layers of code. Making the choice over-constrains the system. No one can predict what the right answer will be.

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 27, 2020

Thanks for your comments @dwalend,

The next big step forward in performance is keeping the file handle open, at least on the NIO front. It's a big boost for not a lot of new concepts or complexity.

Tomorrow, I will clean the branch and I will merge to master and release the library in order to close this issue too:
#739

Tomorrow, I will open a new issue to begin with the next level of optimisation.

I especially want to discourage anything that involves making decisions about concurrency. The big lesson we learned while writing and proofing Concurrency in Practice was that concurrency was a really hard systemic problem best solved in outermost layers of code.

Agree with you.

In the next iteration, we will remove the following blocking delay in a more elegant way:

        this.detect(LEGO_PORT, port);
        if (log.isDebugEnabled()) {
            log.debug("Setting port in mode: {}", TACHO_MOTOR);
        }
        this.setStringAttribute(MODE, TACHO_MOTOR);
        Delay.msDelay(1000);
        this.detect(TACHO_MOTOR, port);

Thanks @dwalend and @JakubVanek for the contributions.

Juan Antonio

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 27, 2020

In progress:
#761

@jabrena
Copy link
Member

@jabrena jabrena commented Dec 27, 2020

Merged with success in Github Actions.
https://github.com/ev3dev-lang-java/ev3dev-lang-java/actions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants
You can’t perform that action at this time.