diff --git a/ChangeLog b/ChangeLog index df1f78aaa..566f277da 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * added support for fadvise/F_RDADVISE for improved disk read performance * introduced pop_alerts() which pops the entire alert queue in a single call * support saving metadata in resume file, enable it by default for magnet links * support for receiving multi announce messages for local peer discovery diff --git a/docs/manual.rst b/docs/manual.rst index 311b069ee..1ce3efcba 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -4404,6 +4404,7 @@ session_settings bool always_send_user_agent; bool apply_ip_filter_to_trackers; int read_job_every; + use_disk_read_ahead; }; ``version`` is automatically set to the libtorrent version you're using @@ -5268,6 +5269,10 @@ download rate is enough to saturate the disk, there's a risk the read jobs will never be serviced. With this setting, every *x* write job, issued in a row, will instead pick one read job off of the sorted queue, where *x* is ``read_job_every``. +``use_disk_read_ahead`` defaults to true and will attempt to optimize disk reads +by giving the operating system heads up of disk read requests as they are queued +in the disk job queue. This gives a significant performance boost for seeding. + pe_settings =========== diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 437313e75..e069bd107 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -933,6 +933,7 @@ int main(int argc, char* argv[]) using namespace libtorrent; session_settings settings; + settings.use_disk_read_ahead = false; settings.user_agent = "client_test/" LIBTORRENT_VERSION; settings.choking_algorithm = session_settings::auto_expand_choker; //settings.announce_to_all_trackers = true; diff --git a/include/libtorrent/file.hpp b/include/libtorrent/file.hpp index 77523af8f..dd5fd1f12 100644 --- a/include/libtorrent/file.hpp +++ b/include/libtorrent/file.hpp @@ -241,6 +241,7 @@ namespace libtorrent size_type writev(size_type file_offset, iovec_t const* bufs, int num_bufs, error_code& ec); size_type readv(size_type file_offset, iovec_t const* bufs, int num_bufs, error_code& ec); + void hint_read(size_type file_offset, int len); size_type get_size(error_code& ec) const; diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp index c07c22493..0fd3db5f3 100644 --- a/include/libtorrent/session_settings.hpp +++ b/include/libtorrent/session_settings.hpp @@ -268,6 +268,7 @@ namespace libtorrent , always_send_user_agent(false) , apply_ip_filter_to_trackers(true) , read_job_every(10) + , use_disk_read_ahead(true) {} // libtorrent version. Used for forward binary compatibility @@ -1070,6 +1071,10 @@ namespace libtorrent // write jobs have been taking priority in a row, service // one read job int read_job_every; + + // issue posix_fadvise() or fcntl(F_RDADVISE) for disk reads + // ahead of time + bool use_disk_read_ahead; }; #ifndef TORRENT_DISABLE_DHT diff --git a/include/libtorrent/storage.hpp b/include/libtorrent/storage.hpp index 1fb683d28..de2c8598d 100644 --- a/include/libtorrent/storage.hpp +++ b/include/libtorrent/storage.hpp @@ -115,6 +115,7 @@ namespace libtorrent virtual int readv(file::iovec_t const* bufs, int slot, int offset, int num_bufs); virtual int writev(file::iovec_t const* bufs, int slot, int offset, int num_bufs); + virtual void hint_read(int slot, int offset, int len) {} // negative return value indicates an error virtual int read(char* buf, int slot, int offset, int size) = 0; @@ -201,6 +202,7 @@ namespace libtorrent int read(char* buf, int slot, int offset, int size); int write(char const* buf, int slot, int offset, int size); int sparse_end(int start) const; + void hint_read(int slot, int offset, int len); int readv(file::iovec_t const* bufs, int slot, int offset, int num_bufs); int writev(file::iovec_t const* buf, int slot, int offset, int num_bufs); size_type physical_offset(int slot, int offset); @@ -422,6 +424,8 @@ namespace libtorrent int hash_for_slot(int slot, partial_hash& h, int piece_size , int small_piece_size = 0, sha1_hash* small_hash = 0); + void hint_read_impl(int piece_index, int offset, int size); + int read_impl( file::iovec_t* bufs , int piece_index diff --git a/src/disk_io_thread.cpp b/src/disk_io_thread.cpp index d7255dc6b..3748a94fb 100644 --- a/src/disk_io_thread.cpp +++ b/src/disk_io_thread.cpp @@ -1722,6 +1722,11 @@ namespace libtorrent } } + if (m_settings.use_disk_read_ahead && defer) + { + j.storage->hint_read_impl(j.piece, j.offset, j.buffer_size); + } + TORRENT_ASSERT(j.offset >= 0); if (m_settings.allow_reordered_disk_operations && defer) { diff --git a/src/file.cpp b/src/file.cpp index a5c9a0137..be033ff69 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -1014,6 +1014,20 @@ namespace libtorrent #endif + void file::hint_read(size_type file_offset, int len) + { +#if defined POSIX_FADV_WILLNEED + posix_fadvise(m_fd, file_offset, len, POSIX_FADV_WILLNEED); +#elif defined F_RDADVISE + radvisory r; + r.ra_offset = file_offset; + r.ra_count = len; + fcntl(m_fd, F_RDADVISE, &r); +#else + // TODO: is there any way to pre-fetch data from a file on windows? +#endif + } + size_type file::readv(size_type file_offset, iovec_t const* bufs, int num_bufs, error_code& ec) { TORRENT_ASSERT((m_open_mode & rw_mask) == read_only || (m_open_mode & rw_mask) == read_write); diff --git a/src/session.cpp b/src/session.cpp index bea6af323..fb2972016 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -149,6 +149,7 @@ namespace libtorrent set.cache_size = 0; set.cache_buffer_chunk_size = 1; set.use_read_cache = false; + set.use_disk_read_ahead = false; set.close_redundant_connections = true; diff --git a/src/storage.cpp b/src/storage.cpp index 5cca457ac..bd25871a5 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -1011,6 +1011,59 @@ ret: return ret; } + void default_storage::hint_read(int slot, int offset, int size) + { + size_type start = slot * (size_type)m_files.piece_length() + offset; + TORRENT_ASSERT(start + size <= m_files.total_size()); + + size_type file_offset = start; + file_storage::iterator file_iter; + + // TODO: use binary search! + for (file_iter = files().begin();;) + { + if (file_offset < file_iter->size) + break; + + file_offset -= file_iter->size; + ++file_iter; + TORRENT_ASSERT(file_iter != files().end()); + } + + boost::intrusive_ptr file_handle; + int bytes_left = size; + int slot_size = static_cast(m_files.piece_size(slot)); + + if (offset + bytes_left > slot_size) + bytes_left = slot_size - offset; + + TORRENT_ASSERT(bytes_left >= 0); + + int file_bytes_left; + for (;bytes_left > 0; ++file_iter, bytes_left -= file_bytes_left) + { + TORRENT_ASSERT(file_iter != files().end()); + + file_bytes_left = bytes_left; + if (file_offset + file_bytes_left > file_iter->size) + file_bytes_left = (std::max)(static_cast(file_iter->size - file_offset), 0); + + if (file_bytes_left == 0) continue; + + if (file_iter->pad_file) continue; + + error_code ec; + file_handle = open_file(file_iter, file::read_only, ec); + + // failing to hint that we want to read is not a big deal + // just swollow the error and keep going + if (!file_handle || ec) continue; + + file_handle->hint_read(file_offset, file_bytes_left); + file_offset = 0; + } + } + int default_storage::readv(file::iovec_t const* bufs, int slot, int offset , int num_bufs) { @@ -1072,6 +1125,7 @@ ret: size_type file_offset = start; file_storage::iterator file_iter; + // TODO: use binary search! for (file_iter = files().begin();;) { if (file_offset < file_iter->size) @@ -1698,6 +1752,13 @@ ret: m_free_slots.push_back(slot_index); } + void piece_manager::hint_read_impl(int piece_index, int offset, int size) + { + m_last_piece = piece_index; + int slot = slot_for(piece_index); + m_storage->hint_read(slot, offset, size); + } + int piece_manager::read_impl( file::iovec_t* bufs , int piece_index