diff options
Diffstat (limited to '')
-rw-r--r-- | www/fasterthanfs.html | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/www/fasterthanfs.html b/www/fasterthanfs.html new file mode 100644 index 0000000..3a1347d --- /dev/null +++ b/www/fasterthanfs.html @@ -0,0 +1,644 @@ +<!DOCTYPE html> +<html><head> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> +<link href="sqlite.css" rel="stylesheet"> +<title>35% Faster Than The Filesystem</title> +<!-- path= --> +</head> +<body> +<div class=nosearch> +<a href="index.html"> +<img class="logo" src="images/sqlite370_banner.gif" alt="SQLite" border="0"> +</a> +<div><!-- IE hack to prevent disappearing logo --></div> +<div class="tagline desktoponly"> +Small. Fast. Reliable.<br>Choose any three. +</div> +<div class="menu mainmenu"> +<ul> +<li><a href="index.html">Home</a> +<li class='mobileonly'><a href="javascript:void(0)" onclick='toggle_div("submenu")'>Menu</a> +<li class='wideonly'><a href='about.html'>About</a> +<li class='desktoponly'><a href="docs.html">Documentation</a> +<li class='desktoponly'><a href="download.html">Download</a> +<li class='wideonly'><a href='copyright.html'>License</a> +<li class='desktoponly'><a href="support.html">Support</a> +<li class='desktoponly'><a href="prosupport.html">Purchase</a> +<li class='search' id='search_menubutton'> +<a href="javascript:void(0)" onclick='toggle_search()'>Search</a> +</ul> +</div> +<div class="menu submenu" id="submenu"> +<ul> +<li><a href='about.html'>About</a> +<li><a href='docs.html'>Documentation</a> +<li><a href='download.html'>Download</a> +<li><a href='support.html'>Support</a> +<li><a href='prosupport.html'>Purchase</a> +</ul> +</div> +<div class="searchmenu" id="searchmenu"> +<form method="GET" action="search"> +<select name="s" id="searchtype"> +<option value="d">Search Documentation</option> +<option value="c">Search Changelog</option> +</select> +<input type="text" name="q" id="searchbox" value=""> +<input type="submit" value="Go"> +</form> +</div> +</div> +<script> +function toggle_div(nm) { +var w = document.getElementById(nm); +if( w.style.display=="block" ){ +w.style.display = "none"; +}else{ +w.style.display = "block"; +} +} +function toggle_search() { +var w = document.getElementById("searchmenu"); +if( w.style.display=="block" ){ +w.style.display = "none"; +} else { +w.style.display = "block"; +setTimeout(function(){ +document.getElementById("searchbox").focus() +}, 30); +} +} +function div_off(nm){document.getElementById(nm).style.display="none";} +window.onbeforeunload = function(e){div_off("submenu");} +/* Disable the Search feature if we are not operating from CGI, since */ +/* Search is accomplished using CGI and will not work without it. */ +if( !location.origin || !location.origin.match || !location.origin.match(/http/) ){ +document.getElementById("search_menubutton").style.display = "none"; +} +/* Used by the Hide/Show button beside syntax diagrams, to toggle the */ +function hideorshow(btn,obj){ +var x = document.getElementById(obj); +var b = document.getElementById(btn); +if( x.style.display!='none' ){ +x.style.display = 'none'; +b.innerHTML='show'; +}else{ +x.style.display = ''; +b.innerHTML='hide'; +} +return false; +} +var antiRobot = 0; +function antiRobotGo(){ +if( antiRobot!=3 ) return; +antiRobot = 7; +var j = document.getElementById("mtimelink"); +if(j && j.hasAttribute("data-href")) j.href=j.getAttribute("data-href"); +} +function antiRobotDefense(){ +document.body.onmousedown=function(){ +antiRobot |= 2; +antiRobotGo(); +document.body.onmousedown=null; +} +document.body.onmousemove=function(){ +antiRobot |= 2; +antiRobotGo(); +document.body.onmousemove=null; +} +setTimeout(function(){ +antiRobot |= 1; +antiRobotGo(); +}, 100) +antiRobotGo(); +} +antiRobotDefense(); +</script> +<div class=fancy> +<div class=nosearch> +<div class="fancy_title"> +35% Faster Than The Filesystem +</div> +<div class="fancy_toc"> +<a onclick="toggle_toc()"> +<span class="fancy_toc_mark" id="toc_mk">►</span> +Table Of Contents +</a> +<div id="toc_sub"><div class="fancy-toc1"><a href="#summary">1. Summary</a></div> +<div class="fancy-toc2"><a href="#caveats">1.1. Caveats</a></div> +<div class="fancy-toc2"><a href="#related_studies">1.2. Related Studies</a></div> +<div class="fancy-toc1"><a href="#how_these_measurements_are_made">2. How These Measurements Are Made</a></div> +<div class="fancy-toc2"><a href="#read_performance_measurements">2.1. Read Performance Measurements</a></div> +<div class="fancy-toc2"><a href="#write_performance_measurements">2.2. Write Performance Measurements</a></div> +<div class="fancy-toc2"><a href="#variations">2.3. Variations</a></div> +<div class="fancy-toc1"><a href="#general_findings">3. General Findings</a></div> +<div class="fancy-toc1"><a href="#additional_notes">4. Additional Notes</a></div> +<div class="fancy-toc2"><a href="#compiling_and_testing_on_android">4.1. Compiling And Testing on Android</a></div> +</div> +</div> +<script> +function toggle_toc(){ +var sub = document.getElementById("toc_sub") +var mk = document.getElementById("toc_mk") +if( sub.style.display!="block" ){ +sub.style.display = "block"; +mk.innerHTML = "▼"; +} else { +sub.style.display = "none"; +mk.innerHTML = "►"; +} +} +</script> +</div> + + + + + +<h1 id="summary"><span>1. </span>Summary</h1> + +<p>SQLite reads and writes small blobs (for example, thumbnail images) +<a href="#approx">35% faster¹</a> than the same blobs +can be read from or written to individual files on disk using +fread() or fwrite(). + +</p><p>Furthermore, a single SQLite database holding +10-kilobyte blobs uses about 20% less disk space than +storing the blobs in individual files. + +</p><p>The performance difference arises (we believe) because when +working from an SQLite database, the open() and close() system calls +are invoked only once, whereas +open() and close() are invoked once for each blob +when using blobs stored in individual files. It appears that the +overhead of calling open() and close() is greater than the overhead +of using the database. The size reduction arises from the fact that +individual files are padded out to the next multiple of the filesystem +block size, whereas the blobs are packed more tightly into an SQLite +database. + +</p><p> +The measurements in this article were made during the week of 2017-06-05 +using a version of SQLite in between 3.19.2 and 3.20.0. You may expect +future versions of SQLite to perform even better. + +</p><h2 id="caveats"><span>1.1. </span>Caveats</h2> + +<a name="approx"></a> +<p> +¹The 35% figure above is approximate. Actual timings vary +depending on hardware, operating system, and the +details of the experiment, and due to random performance fluctuations +on real-world hardware. See the text below for more detail. +Try the experiments yourself. Report significant deviations on +the <a href="support.html#fx">SQLite forum</a>. +</p> + +<p> +The 35% figure is based on running tests on every machine +that the author has easily at hand. +Some reviewers of this article report that SQLite has higher +latency than direct I/O on their systems. We do not yet understand +the difference. We also see indications that SQLite does not +perform as well as direct I/O when experiments are run using +a cold filesystem cache. + +</p><p> +So let your take-away be this: read/write latency for +SQLite is competitive with read/write latency of individual files on +disk. Often SQLite is faster. Sometimes SQLite is almost +as fast. Either way, this article disproves the common +assumption that a relational database must be slower than direct +filesystem I/O. + +</p><h2 id="related_studies"><span>1.2. </span>Related Studies</h2> + +<p> +<a href="https://www.microsoft.com/en-us/research/people/gray/">Jim Gray</a> +and others studied the read performance of BLOBs +versus file I/O for Microsoft SQL Server and found that reading BLOBs +out of the +database was faster for BLOB sizes less than between 250KiB and 1MiB. +(<a href="https://www.microsoft.com/en-us/research/publication/to-blob-or-not-to-blob-large-object-storage-in-a-database-or-a-filesystem/">Paper</a>). +In that study, the database still stores the filename of the content even +if the content is held in a separate file. So the database is consulted +for every BLOB, even if it is only to extract the filename. In this +article, the key for the BLOB is the filename, so no preliminary database +access is required. Because the database is never used at all when +reading content from individual files in this article, the threshold +at which direct file I/O becomes faster is smaller than it is in Gray's +paper. + +</p><p> +The <a href="intern-v-extern-blob.html">Internal Versus External BLOBs</a> article on this website is an +earlier investigation (circa 2011) that uses the same approach as the +Jim Gray paper — storing the blob filenames as entries in the +database — but for SQLite instead of SQL Server. + + + +</p><h1 id="how_these_measurements_are_made"><span>2. </span>How These Measurements Are Made</h1> + +<p>I/O performance is measured using the +<a href="https://www.sqlite.org/src/file/test/kvtest.c">kvtest.c</a> program +from the SQLite source tree. +To compile this test program, first gather the kvtest.c source file +into a directory with the <a href="amalgamation.html">SQLite amalgamation</a> source +files "sqlite3.c" and "sqlite3.h". Then on unix, run a command like +the following: + +</p><div class="codeblock"><pre>gcc -Os -I. -DSQLITE_DIRECT_OVERFLOW_READ \ + kvtest.c sqlite3.c -o kvtest -ldl -lpthread +</pre></div> + +<p>Or on Windows with MSVC: + +</p><div class="codeblock"><pre>cl -I. -DSQLITE_DIRECT_OVERFLOW_READ kvtest.c sqlite3.c +</pre></div> + +<p>Instructions for compiling for Android +are <a href="#compile-android">shown below</a>. + +</p><p> +Use the resulting "kvtest" program to +generate a test database with 100,000 random uncompressible +blobs, each with a random +size between 8,000 and 12,000 bytes +using a command like this: + +</p><div class="codeblock"><pre>./kvtest init test1.db --count 100k --size 10k --variance 2k +</pre></div> + +<p> +If desired, you can verify the new database by running this command: + +</p><div class="codeblock"><pre>./kvtest stat test1.db +</pre></div> + +<p> +Next, make copies of all the blobs into individual files in a directory +using a command like this: + +</p><div class="codeblock"><pre>./kvtest export test1.db test1.dir +</pre></div> + +<p> +At this point, you can measure the amount of disk space used by +the test1.db database and the space used by the test1.dir directory +and all of its content. On a standard Ubuntu Linux desktop, the +database file will be 1,024,512,000 bytes in size and the test1.dir +directory will use 1,228,800,000 bytes of space (according to "du -k"), +about 20% more than the database. + +</p><p> +The "test1.dir" directory created above puts all the blobs into a single +folder. It was conjectured that some operating systems would perform +poorly when a single directory contains 100,000 objects. To test this, +the kvtest program can also store the blobs in a hierarchy of folders with no +more than 100 files and/or subdirectories per folder. The alternative +on-disk representation of the blobs can be created using the --tree +command-line option to the "export" command, like this: + +</p><div class="codeblock"><pre>./kvtest export test1.db test1.tree --tree +</pre></div> + +<p> +The test1.dir directory will contain 100,000 files +with names like "000000", "000001", "000002" and so forth but the +test1.tree directory will contain the same files in subdirectories like +"00/00/00", "00/00/01", and so on. The test1.dir and test1.test +directories take up approximately the same amount of space, though +test1.test is very slightly larger due to the extra directory entries. + +</p><p> +All of the experiments that follow operate the same with either +"test1.dir" or "test1.tree". Very little performance difference is +measured in either case, regardless of operating system. + +</p><p> +Measure the performance for reading blobs from the database and from +individual files using these commands: + +</p><div class="codeblock"><pre>./kvtest run test1.db --count 100k --blob-api +./kvtest run test1.dir --count 100k --blob-api +./kvtest run test1.tree --count 100k --blob-api +</pre></div> + +<p> +Depending on your hardware and operating system, you should see that reads +from the test1.db database file are about 35% faster than reads from +individual files in the test1.dir or test1.tree folders. Results can vary +significantly from one run to the next due to caching, so it is advisable +to run tests multiple times and take an average or a worst case or a best +case, depending on your requirements. + +</p><p>The --blob-api option on the database read test causes kvtest to use +the <a href="c3ref/blob_read.html">sqlite3_blob_read()</a> feature of SQLite to load the content of the +blobs, rather than running pure SQL statements. This helps SQLite to run +a little faster on read tests. You can omit that option to compare the +performance of SQLite running SQL statements. +In that case, the SQLite still out-performs direct reads, though +by not as much as when using <a href="c3ref/blob_read.html">sqlite3_blob_read()</a>. +The --blob-api option is ignored for tests that read from individual disk +files. + +</p><p> +Measure write performance by adding the --update option. This causes +the blobs are overwritten in place with another random blob of +exactly the same size. + +</p><div class="codeblock"><pre>./kvtest run test1.db --count 100k --update +./kvtest run test1.dir --count 100k --update +./kvtest run test1.tree --count 100k --update +</pre></div> + +<p> +The writing test above is not completely fair, since SQLite is doing +<a href="transactional.html">power-safe transactions</a> whereas the direct-to-disk writing is not. +To put the tests on a more equal footing, add either the --nosync +option to the SQLite writes to disable calling fsync() or +FlushFileBuffers() to force content to disk, or using the --fsync option +for the direct-to-disk tests to force them to invoke fsync() or +FlushFileBuffers() when updating disk files. + +</p><p> +By default, kvtest runs the database I/O measurements all within +a single transaction. Use the --multitrans option to run each blob +read or write in a separate transaction. The --multitrans option makes +SQLite much slower, and uncompetitive with direct disk I/O. This +option proves, yet again, that to get the most performance out of +SQLite, you should group as much database interaction as possible within +a single transaction. + +</p><p> +There are many other testing options, which can be seen by running +the command: + +</p><div class="codeblock"><pre>./kvtest help +</pre></div> + +<h2 id="read_performance_measurements"><span>2.1. </span>Read Performance Measurements</h2> + +<p>The chart below shows data collected using +<a href="https://www.sqlite.org/src/file/test/kvtest.c">kvtest.c</a> on five different +systems: + +</p><ul> +<li><b>Win7</b>: A circa-2009 Dell Inspiron laptop, Pentium dual-core + at 2.30GHz, 4GiB RAM, Windows7. +</li><li><b>Win10</b>: A 2016 Lenovo YOGA 910, Intel i7-7500 at 2.70GHz, + 16GiB RAM, Windows10. +</li><li><b>Mac</b>: A 2015 MacBook Pro, 3.1GHz intel Core i7, 16GiB RAM, + MacOS 10.12.5 +</li><li><b>Ubuntu</b>: Desktop built from Intel i7-4770K at 3.50GHz, 32GiB RAM, + Ubuntu 16.04.2 LTS +</li><li><b>Android</b>: Galaxy S3, ARMv7, 2GiB RAM +</li></ul> + +<p>All machines use SSD except Win7 which has a +hard-drive. The test database is 100K blobs with sizes uniformly +distributed between 8K and 12K, for a total of about 1 gigabyte +of content. The database page size +is 4KiB. The -DSQLITE_DIRECT_OVERFLOW_READ compile-time option was +used for all of these tests. +Tests were run multiple times. +The first run was used to warm up the cache and its timings were discarded. + +</p><p> +The chart below shows average time to read a blob directly from the +filesystem versus the time needed to read the same blob from the SQLite +database. +The actual timings vary considerably from one system to another +(the Ubuntu desktop is much +faster than the Galaxy S3 phone, for example). +This chart shows the ratio of the +times needed to read blobs from a file divided by the time needed to +from the database. The left-most column in the chart is the normalized +time to read from the database, for reference. + +</p><p> +In this chart, an SQL statement ("SELECT v FROM kv WHERE k=?1") +is prepared once. Then for each blob, the blob key value is bound +to the ?1 parameter and the statement is evaluated to extract the +blob content. + +</p><p> +The chart shows that on Windows10, content can be read from the SQLite +database about 5 times faster than it can be read directly from disk. +On Android, SQLite is only about 35% faster than reading from disk. + +</p><center> +<div class="imgcontainer"> +<img src="images/faster-read-sql.jpg"> +</div> +<br> +Chart 1: SQLite read latency relative to direct filesystem reads.<br> +100K blobs, avg 10KB each, random order using SQL +</center> + +<p> +The performance can be improved slightly by bypassing the SQL layer +and reading the blob content directly using the +<a href="c3ref/blob_read.html">sqlite3_blob_read()</a> interface, as shown in the next chart: + +</p><center> +<div class="imgcontainer"> +<img src="images/faster-read-blobapi.jpg"> +</div> +<br> +Chart 2: SQLite read latency relative to direct filesystem reads.<br> +100K blobs, avg size 10KB, random order<br> +using sqlite3_blob_read(). +</center> + +<p> +Further performance improves can be made by using the +<a href="mmap.html">memory-mapped I/O</a> feature of SQLite. In the next chart, the +entire 1GB database file is memory mapped and blobs are read +(in random order) using the <a href="c3ref/blob_read.html">sqlite3_blob_read()</a> interface. +With these optimizations, SQLite is twice as fast as Android +or MacOS-X and over 10 times faster than Windows. + +</p><center> +<div class="imgcontainer"> +<img src="images/faster-read-mmap.jpg"> +</div> +<br> +Chart 3: SQLite read latency relative to direct filesystem reads.<br> +100K blobs, avg size 10KB, random order<br> +using sqlite3_blob_read() from a memory-mapped database. +</center> + +<p> +The third chart shows that reading blob content out of SQLite can be +twice as fast as reading from individual files on disk for Mac and +Android, and an amazing ten times faster for Windows. + +</p><h2 id="write_performance_measurements"><span>2.2. </span>Write Performance Measurements</h2> + +<p> +Writes are slower. +On all systems, using both direct I/O and SQLite, write performance is +between 5 and 15 times slower than reads. + +</p><p> +Write performance measurements were made by replacing (overwriting) +an entire blob with a different blob. All of the blobs in these +experiment are random and incompressible. Because writes are so much +slower than reads, only 10,000 of the 100,000 blobs in the database +are replaced. The blobs to be replaced are selected at random and +are in no particular order. + +</p><p> +The direct-to-disk writes are accomplished using fopen()/fwrite()/fclose(). +By default, and in all the results shown below, the OS filesystem buffers are +never flushed to persistent storage using fsync() or +FlushFileBuffers(). In other words, there is no attempt to make the +direct-to-disk writes transactional or power-safe. +We found that invoking fsync() or FlushFileBuffers() on each file +written causes direct-to-disk storage +to be about 10 times or more slower than writes to SQLite. + +</p><p> +The next chart compares SQLite database updates in <a href="wal.html">WAL mode</a> +against raw direct-to-disk overwrites of separate files on disk. +The <a href="pragma.html#pragma_synchronous">PRAGMA synchronous</a> setting is NORMAL. +All database writes are in a single transaction. +The timer for the database writes is stopped after the transaction +commits, but before a <a href="wal.html#ckpt">checkpoint</a> is run. +Note that the SQLite writes, unlike the direct-to-disk writes, +are <a href="transactional.html">transactional</a> and <a href="transactional.html">power-safe</a>, though because the synchronous +setting is NORMAL instead of FULL, the transactions are not durable. + +</p><center> +<div class="imgcontainer"> +<img src="images/faster-write-safe.jpg"> +</div> +<br> +Chart 4: SQLite write latency relative to direct filesystem writes.<br> +10K blobs, avg size 10KB, random order,<br> +WAL mode with synchronous NORMAL,<br> +exclusive of checkpoint time +</center> + +<p> +The android performance numbers for the write experiments are omitted +because the performance tests on the Galaxy S3 are so random. Two +consecutive runs of the exact same experiment would give wildly different +times. And, to be fair, the performance of SQLite on android is slightly +slower than writing directly to disk. + +</p><p> +The next chart shows the performance of SQLite versus direct-to-disk +when transactions are disabled (<a href="pragma.html#pragma_journal_mode">PRAGMA journal_mode=OFF</a>) +and <a href="pragma.html#pragma_synchronous">PRAGMA synchronous</a> is set to OFF. These settings put SQLite on an +equal footing with direct-to-disk writes, which is to say they make the +data prone to corruption due to system crashes and power failures. + +</p><center> +<div class="imgcontainer"> +<img src="images/faster-write-unsafe.jpg"> +</div> +<br> +Chart 5: SQLite write latency relative to direct filesystem writes.<br> +10K blobs, avg size 10KB, random order,<br> +journaling disabled, synchronous OFF. +</center> + +<p> +In all of the write tests, it is important to disable anti-virus software +prior to running the direct-to-disk performance tests. We found that +anti-virus software slows down direct-to-disk by an order of magnitude +whereas it impacts SQLite writes very little. This is probably due to the +fact that direct-to-disk changes thousands of separate files which all need +to be checked by anti-virus, whereas SQLite writes only changes the single +database file. + +</p><h2 id="variations"><span>2.3. </span>Variations</h2> + +<p>The <a href="compile.html#direct_overflow_read">-DSQLITE_DIRECT_OVERFLOW_READ</a> compile-time option causes SQLite +to bypass its page cache when reading content from overflow pages. This +helps database reads of 10K blobs run a little faster, but not all that much +faster. SQLite still holds a speed advantage over direct filesystem reads +without the SQLITE_DIRECT_OVERFLOW_READ compile-time option. + +</p><p>Other compile-time options such as using -O3 instead of -Os or +using <a href="compile.html#threadsafe">-DSQLITE_THREADSAFE=0</a> and/or some of the other +<a href="compile.html#rcmd">recommended compile-time options</a> might help SQLite to run even faster +relative to direct filesystem reads. + +</p><p>The size of the blobs in the test data affects performance. +The filesystem will generally be faster for larger blobs, since +the overhead of open() and close() is amortized over more bytes of I/O, +whereas the database will be more efficient in both speed and space +as the average blob size decreases. + + +</p><h1 id="general_findings"><span>3. </span>General Findings</h1> + +<ol type="A"> +<li> +<p>SQLite is competitive with, and usually faster than, blobs stored in +separate files on disk, for both reading and writing. + +</p></li><li> +<p>SQLite is much faster than direct writes to disk on Windows +when anti-virus protection is turned on. Since anti-virus software +is and should be on by default in Windows, that means that SQLite +is generally much faster than direct disk writes on Windows. + +</p></li><li> +<p>Reading is about an order of magnitude faster than writing, for all +systems and for both SQLite and direct-to-disk I/O. + +</p></li><li> +<p>I/O performance varies widely depending on operating system and hardware. +Make your own measurements before drawing conclusions. + +</p></li><li> +<p>Some other SQL database engines advise developers to store blobs in separate +files and then store the filename in the database. In that case, where +the database must first be consulted to find the filename before opening +and reading the file, simply storing the entire blob in the database +gives much faster read and write performance with SQLite. +See the <a href="intern-v-extern-blob.html">Internal Versus External BLOBs</a> article for more information. +</p></li></ol> + + +<h1 id="additional_notes"><span>4. </span>Additional Notes</h1> + +<a name="compile-android"></a> +<h2 id="compiling_and_testing_on_android"><span>4.1. </span>Compiling And Testing on Android</h2> + +<p> +The kvtest program is compiled and run on Android as follows. +First install the Android SDK and NDK. Then prepare a script +named "android-gcc" that looks approximately like this: + +</p><div class="codeblock"><pre>#!/bin/sh +# +NDK=/home/drh/Android/Sdk/ndk-bundle +SYSROOT=$NDK/platforms/android-16/arch-arm +ABIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin +GCC=$ABIN/arm-linux-androideabi-gcc +$GCC --sysroot=$SYSROOT -fPIC -pie $* +</pre></div> + +<p>Make that script executable and put it on your $PATH. Then +compile the kvtest program as follows: + +</p><div class="codeblock"><pre>android-gcc -Os -I. kvtest.c sqlite3.c -o kvtest-android +</pre></div> + +<p>Next, move the resulting kvtest-android executable to the Android +device: + +</p><div class="codeblock"><pre>adb push kvtest-android /data/local/tmp +</pre></div> + +<p>Finally use "adb shell" to get a shell prompt on the Android device, +cd into the /data/local/tmp directory, and begin running the tests +as with any other unix host. +</p><p align="center"><small><i>This page last modified on <a href="https://sqlite.org/docsrc/honeypot" id="mtimelink" data-href="https://sqlite.org/docsrc/finfo/pages/fasterthanfs.in?m=8db5c86b5b4ac3c05">2021-03-01 12:55:48</a> UTC </small></i></p> + |