cache


Frequent I/O operations on a slow hardware device can have a significant impact on overall application performance. Caching is one technique to use to enhance performance by buffering multiple read and write operations into a single step. This buffering reduces the impact of costly device overhead.

The class os_cache is a specialized device adapter that provides read/write caching capabilities to other device adapters. For example, you can use os_cache with os_file to enhance the I/O performance of disk files. You can also use the cache adapter with other communication mechanisms like os_pipe and os_tcp_socket to optimize client/server applications.

The os_cache has a single constructor that accepts these arguments.

Both buffer size arguments default to 4K, but this size can be adjusted for performance. When the cache write buffer fills, it is automatically flushed; when the cache read buffer empties, it is automatically flushed. The os_adapter must refer to an open device that supports both reading and writing; seek capabilities are optional. Although random access operation is supported by os_cache , the class is not optimized for seek operations.

The following example demonstrates the performance benefit of os_cache by executing a variety of read and write operations on both a raw and a cached disk file. The output was generated on a Solaris 2.4 SPARC workstation.

Example <ospace/stream/examples/cache1.cpp>
#include <iostream>
#include <ospace/file.h>
#include <ospace/time.h>

const int BUFFER_SIZE = 1024;
int buffer[ BUFFER_SIZE ];

void
write( os_adapter adapter )
  {
  for ( int loop = 0; loop < BUFFER_SIZE; ++loop )
    for ( int loop2 = 0; loop2 < BUFFER_SIZE; ++loop2 )
      if ( adapter->write( &buffer[loop2], sizeof(int) ) != sizeof(int) )
        cout << "\tError during write!" << endl;
  }

void
read( os_adapter adapter )
  {
  int local[ BUFFER_SIZE ];
  for ( int loop = 0; loop < BUFFER_SIZE; ++loop )
    for ( int loop2 = 0; loop2 < BUFFER_SIZE; ++loop2 )
      if ( adapter->read( &local[loop2], sizeof(int) ) != sizeof(int) )
        cout << "\tError during read!" << endl;
  }

void
test( os_adapter adapter )
  {
  os_stopwatch timer;

  cout << "\tWrite cycle: ";
  timer.start();
  write( adapter );
  adapter->sync();
  timer.stop();
  cout << timer << endl;

  cout << "\tRead cycle: ";
  adapter->seek( 0 );
  timer.reset();
  timer.start();
  read( adapter );
  timer.stop();
  cout << timer << endl;
  }

void
main()
  {
  os_file_toolkit init_file;
  os_time_toolkit init_time;

  // Initialize test data.
  for ( int loop = 0; loop < BUFFER_SIZE; ++loop )
    buffer[ loop ] = loop;

  cout << "Testing raw os_file performance." << endl;
    {
    os_file file
      (
      "cache1.raw",
      os_open_control::create_always,
      os_io_control::read_write_access
      );
    test( file );
    }

  cout << "Testing cached os_file performance." << endl;
    {
    os_file file
      (
      "cache1.bin",
      os_open_control::create_always,
      os_io_control::read_write_access
      );
    os_cache cache
      (
      os_adapter_for( file ),
      40960,
      40960
      );
    test( cache );
    }
  }

Testing raw os_file performance.
        Write cycle: 87.317162 seconds, stopped
        Read cycle: 37.385414 seconds, stopped
Testing cached os_file performance.
        Write cycle: 2.132877 seconds, stopped
        Read cycle: 2.092149 seconds, stopped
	

One of the best applications for os_cache enhances the performance of the Universal Streaming Service when used with a slow device. Because streaming complex data can produce many individual I/O calls, the os_cache class improves execution speed by minimizing the number of physical device operations.

The next example demonstrates how to use the os_cache class with the Universal Streaming Service. Note that the os_bstream class accepts the cache as a device adapter and does not provide any specialized support for the caching mechanism. An os_cache object automatically flushes its contents on destruction. You can also manually flush an os_cache by using sync() .

Example <ospace/stream/examples/cache2.cpp>
#include <iostream>
#include <string>
#include <ospace/file.h>
#include <ospace/stream.h>
#include <ospace/uss/std/string.h>

void
write()
  {
  string anil( "Anil" );
  string drew( "Drew" );
  string paul( "Paul" );

  os_file file
    (
    "cache2.bin",
    os_open_control::create_always,
    os_io_control::write_only_access
    );
  os_cache cache( file );
  os_bstream stream( cache );
  stream << anil << drew << paul;

  cout << "Wrote to stream:" << endl;
  cout << anil << endl;
  cout << drew << endl;
  cout << paul << endl;
  }

void
read()
  {
  cout << "Read from stream:" << endl;

  os_file file( "cache2.bin" );
  os_cache cache( file );
  os_bstream stream( cache );

  // Read without knowing how many names are coming.
  try
    {
    while ( true )
      {
      string name;
      stream >> name;
      cout << "Read name: " << name << endl;
      }
    }
  catch ( os_streaming_toolkit_error& )
    {
    cout << "Done." << endl;
    }
  }

void
main()
  {
  os_file_toolkit init_file;
  os_streaming_toolkit init_streaming;

  write();
  read();
  }

Wrote to stream:
Anil
Drew
Paul

Read from stream:
Read name: Anil
Read name: Drew
Read name: Paul
Done.

The os_cache class also works as well with other devices like pipes and sockets as it does with files. The next examples use the cache to improve performance of client/server socket communication. Each program is executed twice: once using raw socket calls and then using the cache.

Example <ospace/stream/examples/cache3s.cpp>
// Server example.

#include <iostream>
#include <ospace/network.h>
#include <ospace/stream.h>
#include <ospace/time.h>

const int BUFFER_SIZE = 1024;
const int TIMES = 50;
int buffer[ BUFFER_SIZE ];

void
write( const os_adapter& adapter )
  {
  os_stopwatch timer;
  cout << "Write cycle: ";
  timer.start();
  for ( int loop = 0; loop < TIMES; ++loop )
    for ( int loop2 = 0; loop2 < BUFFER_SIZE; ++loop2 )
      if ( adapter->write( &buffer[ loop2 ], sizeof(int) ) !=            sizeof(int) )
        cout << "Error during write!" << endl;
  timer.stop();
  cout << timer << endl;
  }

void
writer( bool use_cache )
  {
  // Open a server socket on my host at port 6002.
  os_tcp_connection_server server( os_socket_address( 6002 ) );
  os_tcp_socket socket;
  server.accept( socket ); // Accept incoming connection.

  if ( use_cache )
    {
    // Use a cache with a 20K read/write buffer.
    cout << "Using cache" << endl;
    os_cache cache( os_adapter_for( socket ), 20480, 20480 );
    write( cache );
    }
  else
    {
    write( os_adapter_for( socket ) );
    }
  }

void
main( int argc, const char* argv[] )
  {
  os_network_toolkit init_network;
  os_time_toolkit init_time;
  os_streaming_toolkit init_streaming;

  // Initialize test data.
  for ( int loop = 0; loop < BUFFER_SIZE; ++loop )
    buffer[ loop ] = loop;

  bool use_cache = argc > 1;
  writer( use_cache );
  }

The following is the uncached output from this example.

Write cycle: 131.567791 seconds, stopped

The following is the cached output from this example.

Using cache
Write cycle: 0.192626 seconds, stopped
Example <ospace/stream/examples/cache3c.cpp>
// Client example.

#include <iostream>
#include <ospace/network.h>
#include <ospace/stream.h>
#include <ospace/time.h>

const int BUFFER_SIZE = 1024;
const int TIMES = 50;

void
read( const os_adapter& adapter )
  {
  int local[ BUFFER_SIZE ];
  os_stopwatch timer;
  cout << "Read and verify cycle: ";
  timer.start();

  for ( int loop = 0; loop < TIMES; ++loop )
    for ( int loop2 = 0; loop2 < BUFFER_SIZE; ++loop2 )
      {
      if ( adapter->read( &local[ loop2 ], sizeof(int) ) !=

            sizeof(int) )
        cout << "Error during read!" << endl;

      if ( local[ loop2 ] != loop2 )
        cout << "Error during read test!" << endl;
      }
  timer.stop();
  cout << timer << endl;
  }

void
reader( bool use_cache )
  {
  // Connect to server on my host at port 6002.
  os_tcp_socket socket;
  socket.connect_to( os_socket_address( 6002 ) );

  if ( use_cache )
    {
    // Use a cache with a 20K read/write buffering.
    cout << "Using cache" << endl;
    os_cache cache( os_adapter_for( socket ), 20480, 20480 );
    read( cache );
    }
  else
    {
    read( os_adapter_for( socket ) );
    }
  }

void
main( int argc, const char* argv[] )
  {
  os_network_toolkit init_network;
  os_time_toolkit init_time;
  os_streaming_toolkit init_streaming;

  bool use_cache = argc > 1;
  reader( use_cache );
  }

The following is the uncached output from this example.

Read and verify cycle: 152.700224 seconds, stopped

The following is the cached output from this example.

Using cache
Read and verify cycle: 0.206045 seconds, stopped

Copyright©1994-2026 Recursion Software LLC
All Rights Reserved - For use by licensed users only.