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.
#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()
.
#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.
// 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
// 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.