跳至主要内容

Java IO (1): Buffer & Block

Java IO (1): Buffer & Block

Before we start to introduce the IO in Java, we need to understand IO in operating system, which will make us easier to understand the mechanism under the hood and to opt the program.

Basic of IO

User Space & Kernel Space

IO operation can only be done by kernel thread/process, so whenever a user process request an IO operation, it will give the task to kernel to do it. Depends on how user process react when submitting task and waiting for response, there exists many ways to classify:

  • Whether user process is blocked: Block vs Non-block
  • Whether user process get data by polling in loop or by notification: Synchronous vs Asynchronous

We will cover more details and examples about this in this serial of blogs.

Disk Speed

The following is some speed statistic taken from this gist

Latency Comparison Numbers
--------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy             3,000   ns        3 us
Send 1K bytes over 1 Gbps network       10,000   ns       10 us
Read 4K randomly from SSD*             150,000   ns      150 us          ~1GB/sec SSD

From which, we can easily see how cache of disk driver and buffer can speed up performance, and how should we opt our IO performance.

Devices

Operating systems divide IO devices into two categories: character device and block device. Character device is often used to read/write char one by one, while block device is often operating in blocks and buffer. The example of block device is like hard disk, while char device is like serial port.

EOF

What does EOF on a connection mean? The idea of EOF is often confusing to newbie, especially in the context of Internet connections. First, we need to understand that there is no such thing as an EOF character. Rather, EOF is a condition that is detected by the kernel. An application finds out about the EOF condition when it receives a zero return code from the read function. For disk files, EOF occurs when the current file position exceeds the file length. For Internet connections, EOF occurs when a process closes its end of the connection. The process at the other end of the connection detects the EOF when it attempts to read past the last byte in the stream.

IO in Java

Back to the world when Java just came out, it has a strong and complex IO designs. It has two sub types: character stream and byte stream. The IO model make the abstraction that every IO operation is done with a stream of data, and, depending on how to interpret the data, separated into Reader&Writer – character stream and InputStream&OutputStream – byte stream. The full picture of IO army’s hierarchy is like following:

hierarchy
As we can see, each stream has many sub stream types, and thanks to the Decorator design patterns, they can combine with each other to produce much more different streams. Some people criticize the complexity of this design: even a simple write to file can be like following:

BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("file")));
writer.write(123);

Because the functionality (interface) of Reader/Writer is much human friendly, which will convert data in stream into Java data types, people much prefer to use Reader/Writer. So Java IO army provides a bridge to convert the InputStream&OutputStream to Reader&WriterInputStreamReader&OutputStreamWriter.

Limitations of IO

Aside from this inconvenience, Java IO is too important to leave out, so it is still heavily used. But, with the prevalence of Java IO, more drawbacks of this IO model is exposed.

Buffered

The default behavior of this IO model will not use buffer to assist read/write performance, unless user directly add a Buffered decoration deliberately like above usage. The performance distinction can be very large between buffering and no buffering.

When running the following code snippet with a 1MB file, the run time is 1530.x ms, 19.xxx ms, 5.xxx ms on my computer.

FileInputStream fis = new FileInputStream(file);  
int cnt = 0;  
int b;  
while ((b = fis.read()) != -1) {  
  if (b == '\n') {  
    cnt++;  
  }  
}

FileInputStream fis = new FileInputStream(file);  
BufferedInputStream bis = new BufferedInputStream(fis);  
int cnt = 0;  
int b;  
while ((b = bis.read()) != -1) {  
  if (b == '\n') {  
    cnt++;  
  }  
}

FileInputStream fis = new FileInputStream(file);  
byte buf[] = new byte[2048];  
int cnt = 0;  
int n;  
while ((n = fis.read(buf)) != -1) {  
  for (int i = 0; i < n; i++) {  
    if (buf[i] == '\n') {  
      cnt++;  
    }  
  }  
}

And if we need to output to a terminal window, we will met output buffering – line buffering. It is important for a terminal to show printed result when a newline character is encountered. That means, System.out will flush a line if there is a newline. So if we disable line buffering, we can output a larger block of character which will be much faster.

Rules to Opt

From the above examples and trials, we can have following principles when do IO optimizations:

  1. Avoid accessing the disk & underlying operating system.
  2. Avoid method calls.
  3. Avoid processing bytes and characters one by one.

Blocking

Besides the buffering problem, there is another problems: blocking. In old IO model, all IO operation is blocking operation, i.e. program sends the read/write request and waiting until the operation is done. If we need to do multiple IO operation at the same time, we need multiple threads. This works if simultaneous IO operation is not so much, but what if there exists 150000 short IO request at the same time? You may argue that there is no such situation, but there is. Considering a web server which provides request for user, it can be popular and so many user access it at the same time. In this case, a blocking IO model is not suitable, for the additional cost:

  • Too many thread to allocate;
  • Memory consumption of thread (64k or even more), waste of resource;
  • The performance penalty of context switch for different threads;

So, later Java introduced more advanced IO model – Non-blocking IO – to solve this kind of problems and we will cover in next post.

Ref

Written with StackEdit.

评论

此博客中的热门博文

Spring Boot: Customize Environment

Spring Boot: Customize Environment Environment variable is a very commonly used feature in daily programming: used in init script used in startup configuration used by logging etc In Spring Boot, all environment variables are a part of properties in Spring context and managed by Environment abstraction. Because Spring Boot can handle the parse of configuration files, when we want to implement a project which uses yml file as a separate config file, we choose the Spring Boot. The following is the problems we met when we implementing the parse of yml file and it is recorded for future reader. Bind to Class Property values can be injected directly into your beans using the @Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via @ConfigurationProperties. As the document says, there exists three ways to access properties in *.properties or *.yml : @Value : access single value Environment : can access multi

Elasticsearch: Join and SubQuery

Elasticsearch: Join and SubQuery Tony was bothered by the recent change of search engine requirement: they want the functionality of SQL-like join in Elasticsearch! “They are crazy! How can they think like that. Didn’t they understand that Elasticsearch is kind-of NoSQL 1 in which every index should be independent and self-contained? In this way, every index can work independently and scale as they like without considering other indexes, so the performance can boost. Following this design principle, Elasticsearch has little related supports.” Tony thought, after listening their requirements. Leader notice tony’s unwillingness and said, “Maybe it is hard to do, but the requirement is reasonable. We need to search person by his friends, didn’t we? What’s more, the harder to implement, the more you can learn from it, right?” Tony thought leader’s word does make sense so he set out to do the related implementations Application-Side Join “The first implementation

Implement isdigit

It is seems very easy to implement c library function isdigit , but for a library code, performance is very important. So we will try to implement it and make it faster. Function So, first we make it right. int isdigit ( char c) { return c >= '0' && c <= '9' ; } Improvements One – Macro When it comes to performance for c code, macro can always be tried. #define isdigit (c) c >= '0' && c <= '9' Two – Table Upper version use two comparison and one logical operation, but we can do better with more space: # define isdigit(c) table[c] This works and faster, but somewhat wasteful. We need only one bit to represent true or false, but we use a int. So what to do? There are many similar functions like isalpha(), isupper ... in c header file, so we can combine them into one int and get result by table[c]&SOME_BIT , which is what source do. Source code of ctype.h : # define _ISbit(bit) (1 << (