Menu

#7 Enhancement to write speed of RNG_output

v1.0_(example)
closed-fixed
None
5
2017-08-29
2014-11-18
Jirka
No

Hi,

I have verified that RNG_test with stdin got real performance boost by reading the data from stdin by 8kB chunks (as implemented in version 0.92). See the results of performance testing here:

https://sourceforge.net/p/pracrand/discussion/366935/thread/53eefb08/#4e1b/d59b/9345

Can you please apply the same enhancement to fwrite in RNG_output? ./tools/RNG_output.cpp is using at the moment buffer of size 512Bytes. Moving to 8kB buffer size will help to improve the performance.

Thanks
Jirka

Discussion

  • - 2014-11-20

    Per your benchmarking, performance of the combination of RNG_test and RNG_output was fully fixed by buffering in RNG_test. RNG_output already writes 512 bytes at a time, which seems like a reasonable block size.

    Although, I could see advantages to having it write a multiple of the machine page size at a time on some fwrite implementations, I suppose. If I were to revisit that code though, the obvious thing to fix would be the endian portability issue, though little endian seems to be winning these days. Sure, why not.

     
  • Jirka

    Jirka - 2014-11-20

    Is there any drawback in changing the buffer size from 512 Bytes to 16kB? Check the results of fread test:

    https://sourceforge.net/p/pracrand/discussion/366935/thread/53eefb08/#16aa/e664

    Moving to 16kB block size will reduce runtime to fwrite/fread 10GiB from 1.4seconds to 0.5 seconds.

    Regarding endianness - I think the best is to simply fwrite the data. In this case you can simply fread data on the same platform. This is the typical use-case. You would only want to change the endianness of saved binary file when moving to system with other endianness. In this case users need to convert the binary file accordingly.

    I really believe the best is to use plain fwrite without changing endianness. If you start tampering with the endianness there will be lots of confusion and since the consumer applications are most likely using plain fread you will in fact end up with buggy behaviour.

     
  • - 2014-11-23

    I generally try to handle endianness better than that, but got sloppy in the command line tools. But it's actually easy to fix, all I have to do is use the same calls for filling TestBlocks that testing uses, they handle everything correctly.

     
  • Jirka

    Jirka - 2014-11-24

    Hi,

    I have done performance testing by changing the buffer size on line 97 in ./tools/RNG_output.cpp I have achieved the best results with

    Uint64 buffer[2048];
    (buffer size 16kB)

    Writing 16 GiB of data:

    Current version (0.5kiB buffer size)
    $time ./RNG_output sfc64 $(bc <<< 16*2^30) >/dev/null

    real 0m8.300s
    user 0m7.585s
    sys 0m0.692s

    Proposed 16kIB buffer size:
    $time ./RNG_output_2048 sfc64 $(bc <<< 16*2^30) >/dev/null

    real 0m7.687s
    user 0m7.056s
    sys 0m0.620s

    IMHO, this is significant improvement.

    I can achieve even better performance by using
    std::fwrite(&buffer[0], b, 1, stdout);
    instead of
    std::cout.write((char*)&buffer[0], b);

    Again best buffer size is 16kiB

    Uint64 buffer[2048];
    std::fwrite(&buffer[0], b, 1, stdout);

    time ./RNG_output_fwrite_2048 sfc64 $(bc <<< 16*2^30) >/dev/null

    real 0m7.399s
    user 0m6.747s
    sys 0m0.641s

    Based on this I would strongly vote to

    • use buffer size 16kiB
    • use fwrite instead of std::cout.write

    I have also done some minor modifications

    • when number of written bytes is smaller than number of requested bytes, error message will be produced
    • at the end, the total number of bytes written out is printed to stderr. Optionally, we can add verbose flag and output this message only when verbose flag is set.

    The file is included (based on 0.92 version), please have a look.

    Thanks
    Jirka

     
  • Jirka

    Jirka - 2014-11-24

    Hi,

    I have added signal handling so that RNG-output will properly end upon receiving SIGINT, SIGPIPE and SIGTERM signals.

    Examples:
    $./RNG_output sfc64 $(bc <<< 16*2^30) > /dev/full
    I/O error when writing to standard output.: No space left on device
    ERROR: 17179869184 bytes has been requested, could only write out 4096 bytes.
    Info: Total 4.0 KiB has been written out

    $./RNG_output sfc64 $(bc <<< 16*2^30)| head -c 10m > /dev/null
    I/O error when writing to standard output.: Broken pipe
    WARNING: Received signal 13. Closing the application.
    ERROR: 17179869184 bytes has been requested, could only write out 10637312 bytes.
    Info: Total 10.1 MiB has been written out

    Please consider to add it to the official version. RNG_output.cpp is included.

    Thanks
    Jirka

     
  • - 2014-11-26

    I think you clicked on the wrong file when adding that attachment.
    edit: to clarify, you attached RNG_benchmark.cpp the 2nd time instead of a more recent copy of RNG_output.cpp

     

    Last edit: 2014-11-26
    • Jirka

      Jirka - 2014-11-27

      Sorry about that. Correct file is now attached.

       
  • - 2014-11-26

    I edited it a bit, partially based upon your older RNG_output.cpp.
    It's now handles endianness correctly.
    It now prints a message when the number of bytes written is not the number requested, including the perror call that might explain why.
    It does not print a message when nothing unexpected happened. Admittedly it's frequently expected for not all output to be used and thus arguably shouldn't have a message, but one can reasonably argue that if the caller doesn't like that they should use exact values for the number of bytes they want.

    Uint64 n = Uint64(_n);
    enum { BUFFER_BLOCKS = 4 };
    PractRand::Tests::TestBlock buffer[BUFFER_BLOCKS];
    while (n) {
    buffer[0].fill(rng, BUFFER_BLOCKS);
    std::size_t b = PractRand::Tests::TestBlock::SIZE * BUFFER_BLOCKS;
    b = (b > n) ? std::size_t(n) : b;
    std::size_t bytes_written = std::fwrite(&buffer[0], 1, b, stdout);
    if (bytes_written != b) {
    n -= bytes_written;
    if (std::ferror(stdout)) std::perror("I/O error when writing to standard output");
    break;
    }
    else n -= b;
    }
    std::fflush(stdout);
    std::fflush(stderr);// mixing C & C++ stderr output may require a flush?
    if (n) std::cerr << "RNG_output: " << Uint64(_n) << " bytes were requested, but only " << (Uint64(_n) - n) << " bytes were successfully written." << std::endl;

    return 0;

     
  • Jirka

    Jirka - 2014-11-27

    Great!

    What about signal handling? Has it made it into your version? Without signal handling, if RNG_output will receive SIGPIPE signal (as the result of downstream pipe program being terminated) it will be terminated immediately, without writing anything. Simple test is this one:

    ./RNG_output sfc64 $(bc <<< 16*2^30)| head -c 10m > /dev/null

    Without signal handling, RNG_output will end silently, without any error message.

    Regarding the endianness - what exactly is happening with the data? Is it being converted from machine internal endianness (which can be saved and read back with fwrite/fread) to big/little endian format? What about

    ./RNG_output sfc64 $(bc <<< 16*2^30)| ./RNG_test stdin64

    is it going to produce the same results as

    ./RNG_test sfc64 ? And what about

    my_RNG | ./RNG_test stdin

    Will it work the same way as it was till now or do I need to change my_RNG to dump data now in big/little endian format?

    IMHO, whatever binary data you dump or read, the default should be to use the platform native endianness. But perhaps you have meant something different?

    Thanks!
    Jirka

    EDIT: I have just noticed your previous comment about me attaching the wrong file. Sorry about that, I have now attached the correct file. It includes signal handling. See 2 comments above.

     

    Last edit: Jirka 2014-11-27
  • - 2014-11-27

    What about signal handling?

    Haven't added it. I'll take a look at your more recent code and if it looks like can be added with uglifying anything I'll add it. I'm still a little fuzzy on why I should actually directly care about signals here, but sure.

    Regarding the endianness - what exactly is happening with the data?

    Nothing. Nothing is happening to the data. All that's happening is the same thing that RNG_test does for its data when generating data internally - it calls the RNG method for its native output format, and treats the block to be filled as an array of the native output format. This means that anything on the same endianness reading the output stream as the same type will receive the correct sequence of numbers, regardless of what that endianness is.

    Before, the problem was that if it was running on a 32 bit PRNG, then RNG_output would call raw64() returning the 1st 32 bits in the low 32 bits and the 2nd in the high 32 bits, and if the native endianness was big endian, and the caller read it as 32 bit integers, getting the 2nd output before the 1st output, etc. You wouldn't notice (neither would I) since it was fine on little endian, and it was fine on 64 bit PRNGs regardless, but there existed cases in which is was behaving inappropriately.

     
    • Jirka

      Jirka - 2014-11-27

      Thank you for explaining the change to me. In fact, I was wondering why you fill the array by calling raw64() regardless of the generator. Now it makes sense.

      And the good news is that ./RNG_test stdin is not affected:-)

      Thanks!
      Jirka

       
  • - 2014-11-27

    What about signal handling? Has it made it into your version? Without signal handling, if RNG_output will receive SIGPIPE signal (as the result of downstream pipe program being terminated) it will be terminated immediately, without writing anything. Simple test is this one:

    What is this SIGPIPE you speak of? I don't see it in the standard or defined on win32.

    I'm leaning towards no on signal-handling - SIGPIPE is non-portable so far as I can see, and I think default behavior is acceptable on all signals.

     
  • Jirka

    Jirka - 2014-11-27

    SIGINT and SIGPIPE are supported also on WIN32 so you will only need to change code like this to exclude SIGPIPE on WIN32

    prev_handler = signal (SIGINT, my_signal_handler);
    prev_handler = signal (SIGTERM, my_signal_handler);

    ifdef linux

    prev_handler = signal (SIGPIPE, my_signal_handler);

    endif

    Fixed source code is included. It compiles & runs fine on Linux.

    Please consider to include it in the official version. I think that correct error messages are important for applications like RNG_output.

    Thanks
    Jirka

    PS: some info on SIGPIPE

    SIGPIPE
    The SIGPIPE signal is sent to a process when it attempts to write to a pipe without a process connected to the other end.

    It typically happens in situation like this

    $./RNG_output sfc64 $(bc <<< 16*2^30)| head -c 10m > /dev/null

    when RNG_output is writing data to another process via pipe. When the other process is terminated, pipe will be closed and ./RNG_output will receive SIGPIPE.

     
  • - 2017-08-29

    Changes going towards 0.94 for this:
    The output buffer is now in units of TestBlocks (didn't I already do this once?) to fix the endianness issue.
    The buffer size is currently set to 8 kilobytes, as requested.
    The warnings for signals and write errors are disabled - each of these was producing spurious messages on at least one platform/compiler tested.
    The warning when not all bytes of output were used remains. However, you can now request "inf" instead of a specific number of bytes, and if "inf" is requested then the number of bytes desired is set to the maximum for a 64 bit unsigned int, and the message when not all of them are used is suppressed. The message there has also been altered so that users can tell exactly how many bytes were output (otherwise getting rid of such a warning correctly could involve an awful lot of trial and error) and exactly how many bytes were requested (you'd think they'd already know, but since such messages likely show up in a log somewhere maybe not).

    marking issue as resolved.

     

    Last edit: 2017-08-29
  • - 2017-08-29
    • status: open --> closed-fixed
    • assigned_to:
     

Log in to post a comment.

MongoDB Logo MongoDB