Mindoo Blog - Cutting edge technologies - About Java, Lotus Notes and iPhone

  • Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    Karsten Lehmann  25 April 2010 23:52:56
    This is a follow-up article to the first one about using the C API to read Notes view data instead of the Notes Java API provided by the Notes.jar file.

    Disclaimer
    You will see some incredible numbers in this article. Be sure, that we double checked them. This is no fake, although it looks like one. :-)

    Testing our approach on a server
    In the first article, we compared the execution times of the C code with the Notes.jar performance in a test case of reading 15.000 view entries from a local database. The result was 0.93 seconds for the C version and 2.8 seconds for the Java API. Pretty nice. That's a third of the execution time of Notes.jar, but we discussed that this may only make sense in cases where you really have a lot to read, e.g. several thousands of view entries, because there is some initialization overhead for creating a second Notes session and opening the database and view.

    So far, we only had done tests on the local machine and we were curious how well our code would work on a server database.
    Our test server is one of our development machines, which is hosted at an internet service provider and has a 100 mbit connection to the internet. Our home office internet connection speed is 15224 kbit/s downstream and 1164 kbit/s upstream.

    Reading all the view entries using Notes.jar gives us a good opportunity to look for some coffee. It takes 856735 ms, which is more than 14 minutes. There is no passthru server in between. The results were even slower when we were testing with a passthru server and a 1,5 mbit server connection.

    Our code looks like this:

            Session session=NotesFactory.createSession();
            Database db=session.getDatabase(serverName, dbPath);
            View v=db.getView(viewName);
            v.setAutoUpdate(false);
                           
            ViewEntryCollection entryCol=v.getAllEntries();
            ViewEntry entry=entryCol.getFirstEntry();
            while (entry!=null) {
                    if (entry.isValid()) {
                            Vector colValues=entry.getColumnValues();
                            //
                            //... code to dump the values to stdout
                            //
                    }
                    ViewEntry nextEntry=entryCol.getNextEntry();
                    entry.recycle();
                    entry=nextEntry;
            }
            session.recycle();


    So let's see how long our C approach takes... With the implementation of our first article, it takes 433337 ms, which is half the time - 7 minutes. Not bad, but still enough time for coffee.

    Reading more than one view entry at a time
    We were really surprised when we saw that result. We had expected that IBM might have added some caching technologies to read more than one view entry at a time, to optimize throughput on the slow network connection. But our C code was still a lot faster than their approach, and it only read one entry, reported it to our Java code via JNI and then it read the next one.

    But there was still some room for improvement in our solution.
    The C API function NIFReadEntries that we are using to read the data has a parameter to tell the API how many rows should be returned, as long as the provided buffer size is sufficient (we let Notes choose the buffer size for now). Until now, we were using a fixed value of 1. In the next version, this parameter is configurable.

    No time for coffee anymore :-)
    So here are the results, all for reading 15.000 view entries via the C API on a server database with different NIFReadEntries parameter values.
    Again, this is no fake. The numbers are absolutely reproducible:

    Maximum entries to read in NIFReadEntries Duration
    1 433337 ms
    10 44801 ms
    100 5960 ms
    1000 2552 ms


    We are down from 7 minutes to 2,5 seconds just by changing one single parameter in the NIFReadEntries call!

    Performance gain for local environment
    Reading more than one line at a time also speeds up the already fast local access times:

    Maximum entries to read in NIFReadEntries Duration
    1 968 ms
    10 606 ms
    100 587 ms
    1000 573 ms


    Reducing CPU load
    We did some tests to search for memory leaks in our code, in which we read the view about 1000 times. We found out that the CPU load can be reduced from 50% to 5-8% just by doing a Thread.sleep(1) every 200 rows in the Java code. This increased the execution time from 0,5s to 1s.

    Conclusion
    We could not believe our eyes when we saw those results. How on earth can reading a view be 99,7% (335 times) faster using C instead of Notes.jar? 14 minutes used for ECL checks and synchronize blocks? That's impossible.


    Comments

    1Ulrich Krause  26.04.2010 6:07:59  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    Very impressive!

    2Stephan H. Wissel  26.04.2010 6:29:10  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    Could u run a comparison:

    - Classic Java

    - C API

    - napi as provided by XPages

    3Michael Bourak  26.04.2010 9:41:20  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    As you are a Domino DP, just look at the Code Drop 4 java API and you'll see some new method (not implemented yet) that may allow us to specify that buffer... not sure, but let's hope ;-)

    4paul  26.04.2010 13:42:59  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    can you post the C API code just to confirm that it's OK?

    5Nathan T. Freeman  26.04.2010 14:53:30  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    Interesting about the Thread.sleep(1) call. That must be giving the garbage collector a chance to clean up. Surprising that it would impact CPU load so dramatically.

    With my own NAPI vs. Notes.jar multi-threaded view walk, I was already caching 32 rows at a time. Bumping it up to 1000 gives a slight improvement, but since it's designed to run against a local NSF instead of a server, it's not bottlenecked by the network.

    However, adding the .sleep in has almost no benefit on a local process. Over the 15000 entries, the fast walk takes about an extra 20%, while CPU load is pegged at about 97% the entire run.

    6Karsten Lehmann  26.04.2010 15:07:38  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    Some additional facts:

    For the test view and the Notes default cache size, we were able to read about 621 entries in one NIFReadEntries call from the view, so everything higher than 621 should only have an effect in other view designs (e.g. less columns). We haven't tried yet to set our own cache size.

    7Nathan T. Freeman  26.04.2010 16:41:27  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    Performance test off my multi-threaded JNI vs. notes.jar

    4 overall loops: JNI ViewEntry walk (FastVE), notes.jar ViewEntry walk (SlowVE), JNI Document walk (FastDoc), notes.jar Document walk (SlowDoc).

    Running against 4 identical NSFs, each with a single view that shows 15,000 documents.

    Each test run starts 8 jobs of that loop styles, 2 jobs per NSF. The timing number is the average run time for each job.

    Local NSF, FastVE, avg ms: 1700

    Local NSF, SlowVE avg ms: 15000

    Local NSF FastDoc, avg ms: 2000

    Local NSF SlowDoc avg ms: 24000

    Remote server: 100Mbps server about 30 feet from my desk.

    Remote NSF, FastVE, avg ms: 6100

    Remote NSF, SlowVE, avg ms: 132000

    Remote NSF, FastDoc, avg ms: 6200

    Remote NSF, SlowDoc avg ms: 261000

    This is using a ReadEntry block size of 1000.

    Remember, each average is determined by running 8 jobs with the same algorithm concurrently against 4 separate instances of an identical NSF.

    8Bill Wheaton  08.05.2010 20:53:30  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    Nathan, what if you upped the ante to say 1.5 million docs - is it linear?

    9Mikael Paul  20.08.2010 16:21:25  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    What parameters on NIFReadEntries do you use to fix the number of line retrieve at each execution of NIFReadEntries ?

    I try the 6 th parameters ( ReturnCount ) with the value 3FF for a total of 9000 entries, and the method return me 219 lines each times.

    10Karsten Lehmann  20.08.2010 17:16:05  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    The C API uses an internal memory buffer to transfer the data. If the buffer is full, NIFReadEntries cannot return more view entries. In that case it does not matter what you specify as the return count value, e.g. in your case 291, 300 or 9000. The amount of rows that are read in one call depends on your view data. For example, in Nathan Freemans sample database, NIFReadEntries reads 691 entries, while in another database that we tried (from MindPlan - { Link } ), we only get 91.

    You may try to create your own buffer to optimize the throughput. We haven't played with that, because we need a way to let the user stop the read operation. If we read the view in one single step, there no way to cancel the operation AFAIK.

    11Derrick  04.12.2010 1:50:57  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    Is anyone publishing, obviously unsupported, copies of this work. We'd love to give them a try.

    12Karsten Lehmann  17.03.2011 22:49:31  Fast Notes view reading via C API Part 2: How to read views 99.7% faster than Notes.jar

    James Cooper from IBM just published an AppDev wiki article on a feature new in 8.5.2 which makes view reading much faster than in earlier releases WITHOUT using custom C code:

    { Link }