From 4b5357ad04397b09f1acedd81e9439b0af4549ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20H=C3=B6gberg?= Date: Thu, 2 Nov 2017 16:41:26 +0100 Subject: Update file performance advice The parts relating to drivers/ports are now obsolete, and the provided example was far noisier than it had to be; the only relevant metric is the number of calls and it's up to the user to decide how those will be reduced. One could argue for its complete removal, but I'm inclined to leave it be. --- lib/kernel/doc/src/file.xml | 220 +++++++++++++++++--------------------------- 1 file changed, 82 insertions(+), 138 deletions(-) (limited to 'lib') diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml index 2ab35b9b05..3e3c5b3bf3 100644 --- a/lib/kernel/doc/src/file.xml +++ b/lib/kernel/doc/src/file.xml @@ -33,11 +33,14 @@

This module provides an interface to the file system.

-

On operating systems with thread support, - file operations can be performed in threads of their own, allowing - other Erlang processes to continue executing in parallel with - the file operations. See command-line flag - +A in erl(1).

+ +

File operations are only guaranteed to appear atomic when going + through the same file server. A NIF or other OS process may observe + intermediate steps on certain operations on some operating systems, + eg. renaming an existing file on Windows, or + write_file_info/2 + on any OS at the time of writing.

+

Regarding filename encoding, the Erlang VM can operate in two modes. The current mode can be queried using function @@ -1438,8 +1441,12 @@ f.txt: {person, "kalle", 25}. which is 1970-01-01 00:00 UTC.

Default is {time, local}.

-

If the option raw is set, the file server is not called - and only information about local files is returned.

+

If the option raw is set, the file server is not called and + only information about local files is returned. Note that this will + break this module's atomicity guarantees as it can race with a + concurrent call to + write_file_info/1,2 +

As file times are stored in POSIX time on most OS, it is faster to query file information with option posix.

@@ -1687,8 +1694,12 @@ f.txt: {person, "kalle", 25}. except that if Name is a symbolic link, information about the link is returned in the file_info record and the type field of the record is set to symlink.

-

If the option raw is set, the file server is not called - and only information about local files is returned.

+

If the option raw is set, the file server is not called and + only information about local files is returned. Note that this will + break this module's atomicity guarantees as it can race with a + concurrent call to + write_file_info/1,2 +

If Name is not a symbolic link, this function returns the same result as read_file_info/1. On platforms that do not support symbolic links, this function @@ -2148,144 +2159,77 @@ f.txt: {person, "kalle", 25}.

Performance -

Some operating system file operations, for example, a - sync/1 or close/1 on a huge file, can block their - calling thread for seconds. If this affects the emulator main - thread, the response time is no longer in the order of - milliseconds, depending on the definition of "soft" in soft - real-time system.

-

If the device driver thread pool is active, file operations are - done through those threads instead, so the emulator can go on - executing Erlang processes. Unfortunately, the time for serving a - file operation increases because of the extra scheduling required - from the operating system.

-

If the device driver thread pool is disabled or of size 0, large - file reads and writes are segmented into many smaller, which - enable the emulator to serve other processes during the file - operation. This has the same effect as when using the thread - pool, but with larger overhead. Other file operations, for - example, sync/1 or close/1 on a huge file, still are - a problem.

-

For increased performance, raw files are recommended. Raw files - use the file system of the host machine of the node.

+

For increased performance, raw files are recommended.

+

A normal file is really a process so it can be used as an I/O + device (see io). + Therefore, when data is written to a normal file, the sending of the + data to the file process, copies all data that are not binaries. Opening + the file in binary mode and writing binaries is therefore recommended. + If the file is opened on another node, or if the file server runs as + slave to the file server of another node, also binaries are copied.

-

- For normal files (non-raw), the file server is used to find the files, - and if the node is running its file server as slave to the file server - of another node, and the other node runs on some other host machine, - they can have different file systems. - However, this is seldom a problem.

+

Raw files use the file system of the host machine of the node. + For normal files (non-raw), the file server is used to find the files, + and if the node is running its file server as slave to the file server + of another node, and the other node runs on some other host machine, + they can have different file systems. + However, this is seldom a problem.

-

A normal file is really a process so it can be used as an I/O - device (see - io). - Therefore, when data is written to a - normal file, the sending of the data to the file process, copies - all data that are not binaries. Opening the file in binary mode - and writing binaries is therefore recommended. If the file is - opened on another node, or if the file server runs as slave to - the file server of another node, also binaries are copied.

-

Caching data to reduce the number of file operations, or rather - the number of calls to the file driver, generally increases - performance. The following function writes 4 MBytes in 23 - seconds when tested:

+

open/2 can be given the + options delayed_write and read_ahead to turn on caching, + which will reduce the number of operating system calls and greatly + improve performance for small reads and writes. However, the overhead + won't disappear completely and it's best to keep the number of file + operations to a minimum. As a contrived example, the following function + writes 4MB in 2.5 seconds when tested:

+ = 0 -> - {ok, FD} = file:open(Name, [raw, write, delayed_write, binary]), - ok = create_file_slow(FD, 0, N), - ok = ?FILE_MODULE:close(FD), - ok. - -create_file_slow(FD, M, M) -> +create_file_slow(Name) -> + {ok, Fd} = file:open(Name, [raw, write, delayed_write, binary]), + create_file_slow_1(Fd, 4 bsl 20), + file:close(Fd). + +create_file_slow_1(_Fd, 0) -> ok; -create_file_slow(FD, M, N) -> - ok = file:write(FD, <>), - create_file_slow(FD, M+1, N).]]> +create_file_slow_1(Fd, M) -> + ok = file:write(Fd, <<0>>), + create_file_slow_1(Fd, M - 1).]]> + +

The following functionally equivalent code writes 128 bytes per call + to write/2 and so does the + same work in 0.08 seconds, which is roughly 30 times faster:

-

The following, functionally equivalent, function collects 1024 - entries into a list of 128 32-byte binaries before each call to - write/2 and so - does the same work in 0.52 seconds, - which is 44 times faster:

= 0 -> - {ok, FD} = file:open(Name, [raw, write, delayed_write, binary]), - ok = create_file(FD, 0, N), - ok = ?FILE_MODULE:close(FD), +create_file(Name) -> + {ok, Fd} = file:open(Name, [raw, write, delayed_write, binary]), + create_file_1(Fd, 4 bsl 20), + file:close(Fd), ok. - -create_file(FD, M, M) -> + +create_file_1(_Fd, 0) -> ok; -create_file(FD, M, N) when M + 1024 =< N -> - create_file(FD, M, M + 1024, []), - create_file(FD, M + 1024, N); -create_file(FD, M, N) -> - create_file(FD, M, N, []). - -create_file(FD, M, M, R) -> - ok = file:write(FD, R); -create_file(FD, M, N0, R) when M + 8 =< N0 -> - N1 = N0-1, N2 = N0-2, N3 = N0-3, N4 = N0-4, - N5 = N0-5, N6 = N0-6, N7 = N0-7, N8 = N0-8, - create_file(FD, M, N8, - [<> | R]); -create_file(FD, M, N0, R) -> - N1 = N0-1, - create_file(FD, M, N1, [<> | R]).]]> +create_file_1(Fd, M) when M >= 128 -> + ok = file:write(Fd, <<0:(128)/unit:8>>), + create_file_1(Fd, M - 128); +create_file_1(Fd, M) -> + ok = file:write(Fd, <<0:(M)/unit:8>>), + create_file_1(Fd, M - 1).]]> - -

Trust only your own benchmarks. If the list length in - create_file/2 above is increased, it runs slightly - faster, but consumes more memory and causes more memory - fragmentation. How much this affects your application is - something that this simple benchmark cannot predict.

-

If the size of each binary is increased to 64 bytes, it - also runs slightly faster, but the code is then twice as clumsy. - In the current implementation, binaries larger than 64 bytes are - stored in memory common to all processes and not copied when - sent between processes, while these smaller binaries are stored - on the process heap and copied when sent like any other term.

-

So, with a binary size of 68 bytes, create_file/2 runs - 30 percent slower than with 64 bytes, and causes much more - memory fragmentation. Notice that if the binaries were to be sent - between processes (for example, a non-raw file), the results - would probably be completely different.

-
-

A raw file is really a port. When writing data to a port, it is - efficient to write a list of binaries. It is not needed to - flatten a deep list before writing. On Unix hosts, scatter output, - which writes a set of buffers in one operation, is used when - possible. In this way write(FD, [Bin1, Bin2 | Bin3]) - writes the contents of the binaries without copying the data - at all, except for perhaps deep down in the operating system - kernel.

-

For raw files, pwrite/2 and pread/2 are - efficiently implemented. The file driver is called only once for - the whole operation, and the list iteration is done in the file - driver.

-

The options delayed_write and read_ahead to - open/2 - make the file driver cache data to reduce - the number of operating system calls. The function - create_file/2 in the recent example takes 60 seconds - without option delayed_write, which is 2.6 - times slower.

-

As a bad example, create_file_slow/2 - without options raw, binary, and delayed_write, - meaning it calls open(Name, [write]), needs - 1 min 20 seconds for the job, which is 3.5 times slower than - the first example, and 150 times slower than the optimized - create_file/2.

- -

If an error occurs when accessing an open file with module - io, - the process handling the file exits. The dead - file process can hang if a process tries to access it later. - This will be fixed in a future release.

-
+

When writing data it's generally more efficient to write a list of + binaries rather than a list of integers. It is not needed to + flatten a deep list before writing. On Unix hosts, scatter output, + which writes a set of buffers in one operation, is used when + possible. In this way write(FD, [Bin1, Bin2 | Bin3]) + writes the contents of the binaries without copying the data + at all, except for perhaps deep down in the operating system + kernel.

+ +

If an error occurs when accessing an open file with module + io, the process + handling the file exits. The dead file process can hang if a process + tries to access it later. This will be fixed in a future release. +

+
-- cgit v1.2.3