📜 ⬆️ ⬇️

NIO: between Scylla and Charybdis?

One of the widely discussed properties of the java.NIO framework is non-blocking, which means the ability to perform I / O operations and computations in parallel. If an application that has requested to read a file has a computational task that can be processed before receiving data from the file, then it becomes possible to simultaneously perform these operations. In the case of deferred writing, the possibilities for concurrency are even greater, since when writing, unlike reading, the application does not expect data to arrive.

Note I would like to headline this article “NIO: performance or compatibility?”, But due to well-known limitations it is necessary to quote the names of these ancient Greek old women.

The success factor here is hardware support. Mass storage device controllers such as SATA AHCI (Advanced Host Controller Interface) and NVMe (Non-Volatile Memory Interface for PCI Express) are capable of processing rather long I / O sequences and transfer data between the RAM and the drive in the bus-master mode, without participation of the program executed by the central processor.
')
,   AHCI

Fig.1 A list of commands generated by the AHCI driver in RAM and hardware-interpreted by the controller can contain up to 32 I / O operation descriptors. Illustration from AHCI Specification

Contradictions and solutions


Here we come to another, less obvious, but at the same time very important characteristic of the NIO framework, which is based on two mutually contradictory criteria:

  1. On the one hand, Java, as a cross-platform programming language, is abstracted from the hardware characteristics of the computing system, architecture, and even the capacity of the central processor. Compatibility requires a set of abstractions that reliably separate the application programmer from the low-level details. Suffice it to recall the absence of pointers in Java.
  2. On the other hand, the performance requires a detailed code optimization, its adaptation to the peculiarities of a specific execution environment and the type of hardware used.


Java Native Interface


One alternative solution is to pair Java classes and libraries written in C or assembler. Not to mention here are the native classes that implement the JNI ( Java Native Interface ) interface , based on classical calling conventions standardized for each operating system and supplemented by a mechanism that provides interaction between the JVM and native code. By the way, having mastered the JNI technology, you can get access to pointers, the absence of which in Java sometimes brings inconvenience.

At the same time, the use of such a radical method as the integration of native code, eliminates the cross-platform advantages of Java, dramatically increases the complexity of application development and the likelihood of errors. Multiple platform support, for a JNI solution, will inevitably mean fork constructs with the need to write and maintain a set of libraries, the number of which is equal to the number of supported systems.

Admittedly, there are situations where the use of JNI is necessary. For example, support for some special devices, such as hardware random number generator.

Nio


As it turned out, high-performance code can also be developed in Java, if we optimally design a system of abstractions that encapsulate the hardware resources of the platform.

Consider the operation of reading a file from disk. Obviously, there are two participants in the process: the source of the data (drive or file) and the recipient (buffer in RAM). Everything said below is true for writing a file to disk, only the direction of information transfer is different, the source and the receiver are swapped.

For user applications, the drive or file is represented by OS functions of disk I / O API. We will not consider the possibility of direct programming of disk controller registers by a user application, which has fallen into the past since the days of MS-DOS, for obvious reasons of compatibility and security.

The buffer is the specified memory range in the address space of the application. If to be completely tedious , in a number of high-level platforms, the recipient buffer can be physically located in the cache memory of the central processor, but with the observance of the classic caching rules, without losing the association with the specified range of RAM addresses.

So, the considered objects of the hardware platform are:


By implementing Java classes that directly correspond to the two named components of “objective reality”, you can get a high-performance solution by minimizing the number of auxiliary operations, which the developers of the NIO framework, based on the concept of channels and buffers, did.

Of course, such a tool cannot be implemented without low-level hardware-dependent programming. We say only that the use of this tool eliminates the need for an application programmer.

NIOBench utility


The NIOBench utility, developed by IC Book Labs and designed to measure the mass storage performance of the subsystem, illustrates this using channels and buffers when performing file operations.

NIOBench,     ,

Fig.2 Utility NIOBench, the output results of measuring the speed of reading, writing and copying files on the hard disk of the laptop ASUS N750JK (the data processing uses the median and the arithmetic average)

NIOBench

Fig.3 Text report of the NIOBench utility with detailed logging of results: the effect of caching is obvious

The input-output methods, in particular, the operations of reading, writing and copying files, which are the object of benchmarks, are based on the following architectural elements.


Failure to comply with this recommendation may lead to performance degradation due to the need to convert Java abstractions into native objects that are passed to the OS OS functions for processing. The JNI interface is also used in the NIOBench project to connect the libraries supporting the hardware random number generator based on the RDRAND processor instruction ( an article about this “Java benchmarks: random patterns and regular results” in the writing process).

Summary


The concept of channels and buffers underlying the NIO technology exactly corresponds to the architecture of the storage subsystems, the main functionality of which is reduced to the movement of information between the RAM (buffers) and various drives (channels).

At the same time, no miracle happened, and the low-level work, from which any framework frees the application programmers, is only transferred to the framework developers and system programmers ...

Links


Source: https://habr.com/ru/post/309826/


All Articles