/*
 * [TestBuffered.java]
 *
 * Summary: Discover whether BufferOutputSteam or BufferedWrite in more efficient.
 *
 * Copyright: (c) 2009-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.8+
 *
 * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
 *
 * Version History:
 *  1.0 2010-02-20 initial version
 */
package com.mindprod.example;

import com.mindprod.common18.EIO;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.DecimalFormat;

import static java.lang.System.*;

/**
 * Discover whether BufferOutputSteam or BufferedWrite in more efficient.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.0 2010-02-20 initial version
 * @since 2010-02-20
 */
public final class TestBuffered
    {
    /**
     * buffer size in chars
     */
    private static final int BUFFSIZE = 32 * 1024 /* 32K chars = 64K bytes */;

    /**
     * how many benchmark trials
     */
    private static final int TRIALS = 5;

    /**
     * size of test file
     */
    private static final long FILESIZE = 100L * 1024 * 1024 /* 100 mb */;

    /**
     * allows us to display with commas.
     */
    private static final DecimalFormat df = new DecimalFormat( "###,##0" );

    /**
     * allows us to display seconds to two decimal  places.
     */
    private static final DecimalFormat df2 = new DecimalFormat( "###,##0.00" );

    /**
     * display nanoseconds as seconds to two decimal places.
     *
     * @param nanoseconds how many nanoseconds ( billionths of a second )
     *
     * @return displayable String.
     */
    private static String asSeconds( double nanoseconds )
        {
        return df2.format( nanoseconds / 1000000000d ) + " seconds";
        }

    /**
     * read test file with a BufferedInputStream
     * W O R S T
     *
     * @return time taken in nanoseconds
     * @throws java.io.IOException if trouble reading test file
     */
    private static long withBufferedInputStream() throws IOException
        {
        out.println( "start read using BufferedInputStream" );
        long prevTime = System.nanoTime();
        final FileInputStream fis = new FileInputStream( "temp.txt" );
        final BufferedInputStream bis = new BufferedInputStream( fis, BUFFSIZE * 2 ); /* buffsize in bytes */
        final InputStreamReader isr = new InputStreamReader( bis, EIO.UTF8 );
        int dummy = 0;
        // ints are used for reading chars
        int c;
        while ( ( c = isr.read() ) >= 0 )
            {
            dummy += c;
            }
        isr.close();
        out.println( "end read using BufferedInputStream " + asSeconds( System.nanoTime() - prevTime ) );
        return System.nanoTime() - prevTime;
        }

    /**
     * write test file with BufferedOutputStream
     * W O R S T
     *
     * @return time taken in nanoseconds
     * @throws java.io.IOException if trouble writing file.
     */
    private static long withBufferedOutputStream() throws IOException
        {
        out.println( "start write using BufferedOutputStream" );
        long prevTime = System.nanoTime();
        final FileOutputStream fos = new FileOutputStream( "temp.txt", false /* append */ );
        final BufferedOutputStream bos = new BufferedOutputStream( fos, BUFFSIZE * 2 ); /* buffsize in bytes */
        final OutputStreamWriter osw = new OutputStreamWriter( bos, EIO.UTF8 );
        for ( long i = 0; i < FILESIZE; i++ )
            {
            char c = ( char ) i;
            // avoid surrogate area
            if ( 0xd800 <= c && c <= 0xdfff )
                {
                c &= 0x3ff;
                }
            osw.write( c );
            }
        osw.close();
        out.println( "end write using BufferedOutputStream " + asSeconds( System.nanoTime() - prevTime ) );
        return System.nanoTime() - prevTime;
        }

    /**
     * read test file with a BufferedReader
     *
     * @return time taken in nanoseconds
     * @throws java.io.IOException if trouble reading file.
     */
    private static long withBufferedReader() throws IOException
        {
        out.println( "start read using BufferedReader" );
        long prevTime = System.nanoTime();
        final FileInputStream fis = new FileInputStream( "temp.txt" );
        final InputStreamReader isr = new InputStreamReader( fis, EIO.UTF8 );
        final BufferedReader br = new BufferedReader( isr, BUFFSIZE /* buffsize in chars */ );
        while ( br.read() >= 0 )
            {
            // empty loop
            }
        br.close();
        out.println( "end read using BufferedReader " + asSeconds( System.nanoTime() - prevTime ) );
        return System.nanoTime() - prevTime;
        }

    /**
     * write test file with a BufferedWriter
     *
     * @return time taken in nanoseconds
     * @throws java.io.IOException if trouble writing file.
     */
    private static long withBufferedWriter() throws IOException
        {
        out.println( "start write using BufferedWriter" );
        long prevTime = System.nanoTime();
        final FileOutputStream fos = new FileOutputStream( "temp.txt", false /* append */ );
        final OutputStreamWriter osw = new OutputStreamWriter( fos, EIO.UTF8 );
        final BufferedWriter bw = new BufferedWriter( osw, BUFFSIZE /* buffsize in chars */ );
        for ( long i = 0; i < FILESIZE; i++ )
            {
            char c = ( char ) i;
            // avoid surrogate area
            if ( 0xd800 <= c && c <= 0xdfff )
                {
                c &= 0x3ff;
                }
            bw.write( c );
            }
        bw.close();
        out.println( "end write using BufferedWriter " + asSeconds( System.nanoTime() - prevTime ) );
        return System.nanoTime() - prevTime;
        }

    /**
     * read test file with a DoubleBufferedReader
     * Split the buffer space in two for BufferedInputStream and BufferedReader
     * B E S T  with 50%
     *
     * @param percent of buffer space to give to the BufferedInputStream.
     *
     * @return time taken in nanoseconds
     * @throws java.io.IOException if problem reading file.
     */
    private static long withDoubleBufferedReader( int percent ) throws IOException
        {
        // round to multiple of 16 bytes.
        final int streamBuffSizeInBytes = ( ( BUFFSIZE * 2 * percent / 100 ) + 8 ) / 16 * 16;
        final int readerBuffSizeInChars = BUFFSIZE - streamBuffSizeInBytes / 2;
        out.println( "start read using DoubleBufferedReader "
                     + percent
                     + "% "
                     + streamBuffSizeInBytes
                     + " bytes for BufferedInputStream "
                     + readerBuffSizeInChars
                     + " chars for BufferedReader." );
        long prevTime = System.nanoTime();
        final FileInputStream fis = new FileInputStream( "temp.txt" );
        final BufferedInputStream bis = new BufferedInputStream( fis, streamBuffSizeInBytes ); /* buffsize in bytes */
        final InputStreamReader isr = new InputStreamReader( bis, EIO.UTF8 );
        final BufferedReader br = new BufferedReader( isr, readerBuffSizeInChars /* buffsize in chars */ );
        while ( br.read() >= 0 )
            {
            // empty loop
            }
        br.close();
        out.println( "end read using DoubleBufferedReader " + asSeconds( System.nanoTime() - prevTime ) );
        return System.nanoTime() - prevTime;
        }

    /**
     * write test file with a BufferedWriter and BufferedOutputStream.
     * Split buffer allocation in two for BufferedOutputStream and BufferedWriter
     * B E S T  with 25%
     *
     * @param percent of buffer space to give to the BufferedOutputStream.
     *
     * @return time taken in nanoseconds
     * @throws java.io.IOException if trouble writing file.
     */
    private static long withDoubleBufferedWriter( int percent ) throws IOException
        {
        // round to multiple of 16 bytes.
        final int streamBuffSizeInBytes = ( ( BUFFSIZE * 2 * percent / 100 ) + 8 ) / 16 * 16;
        final int writerBuffSizeInChars = BUFFSIZE - streamBuffSizeInBytes / 2;
        out.println( "start write using DoubleBufferedWriter "
                     + percent
                     + "% "
                     + streamBuffSizeInBytes
                     + " bytes for BufferedOutputStream "
                     + writerBuffSizeInChars
                     + " chars for BufferedWriter." );
        long prevTime = System.nanoTime();
        final FileOutputStream fos = new FileOutputStream( "temp.txt", false /* append */ );
        final BufferedOutputStream bos = new BufferedOutputStream( fos, streamBuffSizeInBytes ); /* buffsize in bytes */
        final OutputStreamWriter osw = new OutputStreamWriter( bos, EIO.UTF8 );
        final BufferedWriter bw = new BufferedWriter( osw, writerBuffSizeInChars  /* buffsize in chars */ );
        for ( long i = 0; i < FILESIZE; i++ )
            {
            char c = ( char ) i;
            // avoid surrogate area
            if ( 0xd800 <= c && c <= 0xdfff )
                {
                c &= 0x3ff;
                }
            bw.write( c );
            }
        bw.close();
        out.println( "end write using DoubleBufferedWriter " + asSeconds( System.nanoTime() - prevTime ) );
        return System.nanoTime() - prevTime;
        }

    /**
     * Benchmark three ways of buffered writing and three ways of buffered reading.
     *
     * @param args not used
     *
     * @throws java.io.IOException if problem writing or reading the test file.
     */
    public static void main( String[] args ) throws IOException
        {
        // accumulated time all trials
        long bw = 0;
        long br = 0;
        long bos = 0;
        long bis = 0;
        long bdw10 = 0;
        long bdr10 = 0;
        long bdw25 = 0;
        long bdr25 = 0;
        long bdw50 = 0;
        long bdr50 = 0;
        long bdw75 = 0;
        long bdr75 = 0;
        for ( int i = 0; i < TRIALS; i++ )
            {
            bw += withBufferedWriter();
            br += withBufferedReader();
            bos += withBufferedOutputStream();
            bis += withBufferedInputStream();
            bdw10 += withDoubleBufferedWriter( 10 );
            bdr10 += withDoubleBufferedReader( 10 );
            bdw25 += withDoubleBufferedWriter( 25 );
            bdr25 += withDoubleBufferedReader( 25 );
            bdw50 += withDoubleBufferedWriter( 50 );
            bdr50 += withDoubleBufferedReader( 50 );
            bdw75 += withDoubleBufferedWriter( 75 );
            bdr75 += withDoubleBufferedReader( 75 );
            }
        //noinspection ResultOfMethodCallIgnored
        new File( "temp.txt" ).delete();
        out.println();
        out.println( "Speeds are in seconds averaged over "
                     + TRIALS
                     + " trials using "
                     + df.format( FILESIZE )
                     + " character files and "
                     + df.format( BUFFSIZE )
                     + " character buffers." );
        out.println();
        out.println( "BufferedWriter " + asSeconds( bw / TRIALS ) );
        out.println( "BufferedOutputStream " + asSeconds( bos / TRIALS ) );
        out.println( "DoubleBufferedWriter 10% to BufferedOutputStream " + asSeconds( bdw10 / TRIALS ) );
        out.println( "DoubleBufferedWriter 25% to BufferedOutputStream " + asSeconds( bdw25 / TRIALS ) );
        out.println( "DoubleBufferedWriter 50% to BufferedOutputStream " + asSeconds( bdw50 / TRIALS ) );
        out.println( "DoubleBufferedWriter 75% to BufferedOutputStream " + asSeconds( bdw75 / TRIALS ) );
        out.println();
        out.println( "BufferedReader " + asSeconds( br / TRIALS ) );
        out.println( "BufferedInputStream " + asSeconds( bis / TRIALS ) );
        out.println( "DoubleBufferedReader 10% to BufferedInputStream " + asSeconds( bdr10 / TRIALS ) );
        out.println( "DoubleBufferedReader 25% to BufferedInputStream " + asSeconds( bdr25 / TRIALS ) );
        out.println( "DoubleBufferedReader 50% to BufferedInputStream " + asSeconds( bdr50 / TRIALS ) );
        out.println( "DoubleBufferedReader 75% to BufferedInputStream " + asSeconds( bdr75 / TRIALS ) );
        // SUN JVM 1.6.0_22 RESULTS 64K char buffer
        // Speeds are in seconds averaged over 3 trials using 104,857,600 character files and 65,536 character buffers.
        // BufferedWriter 4.31 seconds
        // BufferedOutputStream 22.18 seconds W O R S T
        // DoubleBufferedWriter 10% to BufferedOutputStream 4.03 seconds
        // DoubleBufferedWriter 25% to BufferedOutputStream 3.65 seconds B E S T
        // DoubleBufferedWriter 50% to BufferedOutputStream 3.82 seconds
        // DoubleBufferedWriter 75% to BufferedOutputStream 3.68 seconds
        // BufferedReader 3.94 seconds
        // BufferedInputStream 14.11 seconds W O R S T
        // DoubleBufferedReader 10% to BufferedInputStream 4.02 seconds
        // DoubleBufferedReader 25% to BufferedInputStream 3.82 seconds
        // DoubleBufferedReader 50% to BufferedInputStream 3.77 seconds B E S T
        // DoubleBufferedReader 75% to BufferedInputStream 3.81 seconds
        // SUN JVM 1.6.0_22 RESULTS 32K char buffer
        // Speeds are in seconds averaged over 5 trials using 104,857,600 character files and 32,768 character buffers.
        //
        // BufferedWriter 3.96 seconds
        // BufferedOutputStream 20.37 seconds W O R S T
        // DoubleBufferedWriter 10% to BufferedOutputStream 3.81 seconds
        // DoubleBufferedWriter 25% to BufferedOutputStream 3.65 seconds
        // DoubleBufferedWriter 50% to BufferedOutputStream 3.72 seconds
        // DoubleBufferedWriter 75% to BufferedOutputStream 3.59 seconds B E S T
        //
        // BufferedReader 4.00 seconds
        // BufferedInputStream 12.50 seconds W O R S T
        // DoubleBufferedReader 10% to BufferedInputStream 3.96 seconds
        // DoubleBufferedReader 25% to BufferedInputStream 4.05 seconds
        // DoubleBufferedReader 50% to BufferedInputStream 3.95 seconds
        // DoubleBufferedReader 75% to BufferedInputStream 3.94 seconds B E S T
        // JET RESULTS
        // Speeds are in seconds averaged over 3 trials using 104,857,600 character files and 65,536 character buffers.
        //
        // BufferedWriter 4.76 seconds
        // BufferedOutputStream 129.08 seconds W O R S T
        // DoubleBufferedWriter 10% to BufferedOutputStream 7.10 seconds
        // DoubleBufferedWriter 25% to BufferedOutputStream 4.91 seconds
        // DoubleBufferedWriter 50% to BufferedOutputStream 4.64 seconds
        // DoubleBufferedWriter 75% to BufferedOutputStream 4.36 seconds B E S T
        //
        // BufferedReader 5.08 seconds
        // BufferedInputStream 109.91 seconds W O R S T
        // DoubleBufferedReader 10% to BufferedInputStream 16.01 seconds
        // DoubleBufferedReader 25% to BufferedInputStream 5.11 seconds
        // DoubleBufferedReader 50% to BufferedInputStream 5.09 seconds
        // DoubleBufferedReader 75% to BufferedInputStream 5.01 seconds B E S T
        }
    }