]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Avoid millions of rand() calls() when running tests 1438/head
authorAlex Richardson <Alexander.Richardson@cl.cam.ac.uk>
Thu, 17 Sep 2020 17:28:17 +0000 (18:28 +0100)
committerAlex Richardson <Alexander.Richardson@cl.cam.ac.uk>
Sat, 15 Jan 2022 13:47:21 +0000 (13:47 +0000)
Many tests use a loop calling rand() to fill buffers with test data. As
these calls cannot be inlined, this adds up to noticeable overhead:
For example, running on QEMU RISC-V the test_write_format_7zip_large_copy
test took ~22 seconds before and with this change it's ~17 seconds.
This change uses a simpler xorshift64 random number generator that can be
inlined into the loop filling the data buffer. By default the seed for this
RNG is rand(), but it can be overwritten by setting the TEST_RANDOM_SEED
environment variable.

For a native build the difference is much less noticeable, but it's still
measurable: test_write_format_7zip_large_copy takes 314.9 ms ± 3.9 ms
before and 227.8 ms ± 5.8 ms after (i.e. 38% faster for that test).

libarchive/test/test_read_data_large.c
libarchive/test/test_read_extract.c
libarchive/test/test_read_large.c
libarchive/test/test_read_pax_truncated.c
libarchive/test/test_read_truncated.c
libarchive/test/test_read_truncated_filter.c
libarchive/test/test_write_format_7zip_large.c
test_utils/test_utils.c
test_utils/test_utils.h

index 418020d11109c932e62ac64bda7eae793e69cf17..22f46b75dac349fc86ebd80d54ad660d41908a25 100644 (file)
@@ -23,6 +23,7 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 #include "test.h"
+#include "test_utils.h"
 __FBSDID("$FreeBSD: head/lib/libarchive/test/test_read_data_large.c 201247 2009-12-30 05:59:21Z kientzle $");
 
 /*
@@ -49,7 +50,6 @@ DEFINE_TEST(test_read_data_large)
        char tmpfilename[] = "largefile";
        int tmpfilefd;
        FILE *f;
-       unsigned int i;
        size_t used;
 
        /* Create a new archive in memory. */
@@ -64,8 +64,7 @@ DEFINE_TEST(test_read_data_large)
        assert((ae = archive_entry_new()) != NULL);
        archive_entry_copy_pathname(ae, "file");
        archive_entry_set_mode(ae, S_IFREG | 0755);
-       for (i = 0; i < sizeof(buff2); i++)
-               buff2[i] = (unsigned char)rand();
+       fill_with_pseudorandom_data(buff2, sizeof(buff2));
        archive_entry_set_size(ae, sizeof(buff2));
        assertA(0 == archive_write_header(a, ae));
        archive_entry_free(ae);
index 2b1a21e4715b0d11a7980774555a6bf184c0674e..c3e0594e9ae8ebc22a3261c92e8ffd4709704c97 100644 (file)
@@ -23,6 +23,7 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 #include "test.h"
+#include "test_utils.h"
 __FBSDID("$FreeBSD: head/lib/libarchive/test/test_read_extract.c 201247 2009-12-30 05:59:21Z kientzle $");
 
 #define BUFF_SIZE 1000000
@@ -59,8 +60,7 @@ DEFINE_TEST(test_read_extract)
        assert((ae = archive_entry_new()) != NULL);
        archive_entry_copy_pathname(ae, "file");
        archive_entry_set_mode(ae, S_IFREG | 0755);
-       for (i = 0; i < FILE_BUFF_SIZE; i++)
-               file_buff[i] = (unsigned char)rand();
+       fill_with_pseudorandom_data(file_buff, FILE_BUFF_SIZE);
        archive_entry_set_size(ae, FILE_BUFF_SIZE);
        assertA(0 == archive_write_header(a, ae));
        assertA(FILE_BUFF_SIZE == archive_write_data(a, file_buff, FILE_BUFF_SIZE));
index 6966ccbe1916d50de0567fa7d91d72b0d23b0a31..2a4c8e867b10c5e964caf8a3c39b96e8407031b6 100644 (file)
@@ -23,6 +23,7 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 #include "test.h"
+#include "test_utils.h"
 __FBSDID("$FreeBSD: head/lib/libarchive/test/test_read_large.c 201247 2009-12-30 05:59:21Z kientzle $");
 
 static unsigned char testdata[10 * 1024 * 1024];
@@ -37,7 +38,6 @@ static unsigned char buff[11 * 1024 * 1024];
 /* Check correct behavior on large reads. */
 DEFINE_TEST(test_read_large)
 {
-       unsigned int i;
        int tmpfilefd;
        char tmpfilename[] = "test-read_large.XXXXXX";
        size_t used;
@@ -45,8 +45,7 @@ DEFINE_TEST(test_read_large)
        struct archive_entry *entry;
        FILE *f;
 
-       for (i = 0; i < sizeof(testdata); i++)
-               testdata[i] = (unsigned char)(rand());
+       fill_with_pseudorandom_data(testdata, sizeof(testdata));
 
        assert(NULL != (a = archive_write_new()));
        assertA(0 == archive_write_set_format_ustar(a));
index 1f6e78ace2027387197e5e98b89c9f556ece6696..a6705fa9c30a78ac88f266506adcf70d1b19e74b 100644 (file)
@@ -23,6 +23,7 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 #include "test.h"
+#include "test_utils.h"
 __FBSDID("$FreeBSD: head/lib/libarchive/test/test_read_pax_truncated.c 189483 2009-03-07 03:34:34Z kientzle $");
 
 DEFINE_TEST(test_read_pax_truncated)
@@ -48,8 +49,8 @@ DEFINE_TEST(test_read_pax_truncated)
        assert((ae = archive_entry_new()) != NULL);
        archive_entry_copy_pathname(ae, "file");
        archive_entry_set_mode(ae, S_IFREG | 0755);
-       for (i = 0; i < filedata_size; i++)
-               filedata[i] = (unsigned char)rand();
+       fill_with_pseudorandom_data(filedata, filedata_size);
+
        archive_entry_set_atime(ae, 1, 2);
        archive_entry_set_ctime(ae, 3, 4);
        archive_entry_set_mtime(ae, 5, 6);
index 3991ab2baa61d0b6bfefcca47a1166ef8411c06b..117c12ade154be435abc38355b81f6779a7e9908 100644 (file)
@@ -23,6 +23,7 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 #include "test.h"
+#include "test_utils.h"
 __FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_truncated.c,v 1.4 2008/09/01 05:38:33 kientzle Exp $");
 
 static char buff[1000000];
@@ -47,8 +48,7 @@ DEFINE_TEST(test_read_truncated)
        assert((ae = archive_entry_new()) != NULL);
        archive_entry_copy_pathname(ae, "file");
        archive_entry_set_mode(ae, S_IFREG | 0755);
-       for (i = 0; i < sizeof(buff2); i++)
-               buff2[i] = (unsigned char)rand();
+       fill_with_pseudorandom_data(buff2, sizeof(buff2));
        archive_entry_set_size(ae, sizeof(buff2));
        assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
        archive_entry_free(ae);
index 4c409cc0004c8bfe88a7fb41167b6bba2013a859..59a6dc8521877527023fe04b3f968f84db9435d2 100644 (file)
@@ -25,6 +25,7 @@
  */
 
 #include "test.h"
+#include "test_utils.h"
 __FBSDID("$FreeBSD$");
 
 /*
@@ -41,7 +42,7 @@ test_truncation(const char *compression,
        char path[16];
        char *buff, *data;
        size_t buffsize, datasize, used1;
-       int i, j, r, use_prog;
+       int i, r, use_prog;
 
        buffsize = 2000000;
        assert(NULL != (buff = (char *)malloc(buffsize)));
@@ -91,9 +92,7 @@ test_truncation(const char *compression,
                        free(buff);
                        return;
                }
-               for (j = 0; j < (int)datasize; ++j) {
-                       data[j] = (char)(rand() % 256);
-               }
+               fill_with_pseudorandom_data(data, datasize);
                failure("%s", path);
                if (!assertEqualIntA(a, datasize,
                    archive_write_data(a, data, datasize))) {
@@ -111,8 +110,13 @@ test_truncation(const char *compression,
        assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
        assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
 
-       assertEqualIntA(a, ARCHIVE_OK,
-           archive_read_open_memory(a, buff, used1 - used1/64));
+       r = archive_read_open_memory(a, buff, used1 - used1/64);
+       if (r != ARCHIVE_OK) {
+               assertEqualStringA(a, "truncated bzip2 input",
+                   archive_error_string(a));
+                   goto out;
+       }
+
        for (i = 0; i < 100; i++) {
                if (ARCHIVE_OK != archive_read_next_header(a, &ae)) {
                        failure("Should have non-NULL error message for %s",
@@ -133,6 +137,7 @@ test_truncation(const char *compression,
            archive_read_close(a));
        assertEqualInt(ARCHIVE_OK, archive_read_free(a));
 
+out:
        free(data);
        free(buff);
 }
index 5c49f59a1d4d88e87cc98760e7cd8a418cc012e9..be344a3ef98ef63af00a032f62a84f27e824787d 100644 (file)
@@ -25,6 +25,7 @@
 
 
 #include "test.h"
+#include "test_utils.h"
 __FBSDID("$FreeBSD$");
 
 #define LARGE_SIZE     (16*1024*1024)
@@ -37,7 +38,6 @@ test_large(const char *compression_type)
        size_t buffsize = LARGE_SIZE + 1024 * 256;
        size_t datasize = LARGE_SIZE;
        char *buff, *filedata, *filedata2;
-       unsigned i;
 
        assert((buff = malloc(buffsize)) != NULL);
        assert((filedata = malloc(datasize)) != NULL);
@@ -87,8 +87,7 @@ test_large(const char *compression_type)
                /* NOTE: PPMd cannot handle random data correctly.*/
                memset(filedata, 'a', datasize);
        } else {
-               for (i = 0; i < datasize; i++)
-                       filedata[i] = (char)rand();
+               fill_with_pseudorandom_data(filedata, datasize);
        }
        assertEqualInt(datasize, archive_write_data(a, filedata, datasize));
 
index 8ea3d3c4b33d56d8f259c2798f460352a5fe7330..db6c31b2a95e093a6601b74f94304bddd37b654f 100644 (file)
 
 #include "test_utils.h"
 
+#include <errno.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
+#include <assert.h>
 
 /* Filter tests against a glob pattern. Returns non-zero if test matches
  * pattern, zero otherwise. A '^' at the beginning of the pattern negates
@@ -122,3 +125,81 @@ int get_test_set(int *test_set, int limit, const char *test,
        }
        return ((idx == 0)?-1:idx);
 }
+
+static inline uint64_t
+xorshift64(uint64_t *state)
+{
+       uint64_t x = *state;
+       x ^= x << 13;
+       x ^= x >> 7;
+       x ^= x << 17;
+       *state = x;
+       return (x);
+}
+
+/*
+ * Fill a buffer with reproducible pseudo-random data using a simple xorshift
+ * algorithm. Originally, most tests filled buffers with a loop that calls
+ * rand() once for each byte. However, this initialization can be extremely
+ * slow when running on emulated platforms such as QEMU where 16M calls to
+ * rand() take a long time: Before the test_write_format_7zip_large_copy test
+ * took ~22 seconds, whereas using a xorshift random number generator (that can
+ * be inlined) reduces it to ~17 seconds on QEMU RISC-V.
+ */
+void
+fill_with_pseudorandom_data_seed(uint64_t seed, void *buffer, size_t size)
+{
+       uint64_t *aligned_buffer;
+       size_t num_values;
+       size_t i;
+       size_t unaligned_suffix;
+       size_t unaligned_prefix = 0;
+       /*
+        * To avoid unaligned stores we only fill the aligned part of the buffer
+        * with pseudo-random data and fill the unaligned prefix with 0xab and
+        * the suffix with 0xcd.
+        */
+       if ((uintptr_t)buffer % sizeof(uint64_t)) {
+               unaligned_prefix =
+                   sizeof(uint64_t) - (uintptr_t)buffer % sizeof(uint64_t);
+               aligned_buffer =
+                   (uint64_t *)((char *)buffer + unaligned_prefix);
+               memset(buffer, 0xab, unaligned_prefix);
+       } else {
+               aligned_buffer = (uint64_t *)buffer;
+       }
+       assert((uintptr_t)aligned_buffer % sizeof(uint64_t) == 0);
+       num_values = (size - unaligned_prefix) / sizeof(uint64_t);
+       unaligned_suffix =
+           size - unaligned_prefix - num_values * sizeof(uint64_t);
+       for (i = 0; i < num_values; i++) {
+               aligned_buffer[i] = xorshift64(&seed);
+       }
+       if (unaligned_suffix) {
+               memset((char *)buffer + size - unaligned_suffix, 0xcd,
+                   unaligned_suffix);
+       }
+}
+
+void
+fill_with_pseudorandom_data(void *buffer, size_t size)
+{
+       uint64_t seed;
+       const char* seed_str;
+       /*
+        * Check if a seed has been specified in the environment, otherwise fall
+        * back to using rand() as a seed.
+        */
+       if ((seed_str = getenv("TEST_RANDOM_SEED")) != NULL) {
+               errno = 0;
+               seed = strtoull(seed_str, NULL, 10);
+               if (errno != 0) {
+                       fprintf(stderr, "strtoull(%s) failed: %s", seed_str,
+                           strerror(errno));
+                       seed = rand();
+               }
+       } else {
+               seed = rand();
+       }
+       fill_with_pseudorandom_data_seed(seed, buffer, size);
+}
index 164c528fc1de7a5c50295834444889b3b4576e92..3f61f6b2f7fa20f65c98adaaff1a42772c8380dd 100644 (file)
@@ -27,6 +27,9 @@
 #ifndef TEST_UTILS_H
 #define TEST_UTILS_H
 
+#include <stddef.h>
+#include <stdint.h>
+
 struct test_list_t
 {
   void (*func)(void);
@@ -35,5 +38,7 @@ struct test_list_t
 };
 
 int get_test_set(int *, int, const char *, struct test_list_t *);
+void fill_with_pseudorandom_data(void* buffer, size_t size);
+void fill_with_pseudorandom_data_seed(uint64_t seed, void* buffer, size_t size);
 
 #endif /* TEST_UTILS_H */