xref: /6.6.0/platform/tests/crc32/crc32c_bench.cc (revision 87c134a8)
1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2015 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17
18// Benchmark the crc32c functions
19
20#include <platform/crc32c.h>
21#include <platform/timeutils.h>
22
23#include <chrono>
24#include <cstdint>
25#include <cstring>
26#include <iomanip>
27#include <iostream>
28#include <random>
29#include <sstream>
30#include <string>
31#include <vector>
32
33using DurationVector = std::vector<std::chrono::steady_clock::duration>;
34typedef uint32_t (*crc32c_function)(const uint8_t* buf, size_t len, uint32_t crc_in);
35
36static std::vector<std::string> column_heads(0);
37
38void crc_results_banner() {
39    column_heads.push_back("Data size (bytes) ");
40    column_heads.push_back("SW ns      ");
41    column_heads.push_back("SW GiB/s   ");
42    column_heads.push_back("HW ns      ");
43    column_heads.push_back("HW GiB/s   ");
44    column_heads.push_back("HW vs SW ");
45    column_heads.push_back("HW opt ns  ");
46    column_heads.push_back("HW opt GiB/s ");
47    column_heads.push_back("HW vs HW opt ");
48    column_heads.push_back("SW vs HW opt ");
49    for (auto str : column_heads) {
50        std::cout << str << ": ";
51    }
52    std::cout << std::endl;
53}
54
55std::string gib_per_sec(size_t test_size,
56                        std::chrono::steady_clock::duration t) {
57    double gib_per_sec = 0.0;
58    if (t != std::chrono::steady_clock::duration::zero()) {
59        auto one_sec = std::chrono::nanoseconds(std::chrono::seconds(1));
60        auto how_many_per_sec = one_sec / t;
61
62        double bytes_per_sec = static_cast<double>(test_size * how_many_per_sec);
63        gib_per_sec = bytes_per_sec/(1024.0*1024.0*1024.0);
64    }
65    std::stringstream ss;
66    ss << std::fixed << std::setprecision(3) << gib_per_sec;
67    return ss.str();
68}
69
70//
71// Return a/b with an 'x' appended
72// Allows us to print 2.0x when a is twice the size of b
73//
74std::string get_ratio_string(std::chrono::steady_clock::duration a,
75                             std::chrono::steady_clock::duration b) {
76    double ratio = static_cast<double>(a.count()) / b.count();
77    std::stringstream ss;
78    ss << std::fixed << std::setprecision(3) << ratio << "x";
79    return ss.str();
80}
81
82void crc_results(size_t test_size,
83                 DurationVector& timings_sw,
84                 DurationVector& timings_hw,
85                 DurationVector& timings_hw_opt) {
86    std::chrono::steady_clock::duration avg_sw(0), avg_hw(0), avg_hw_opt(0);
87    for(auto duration : timings_sw) {
88        avg_sw += duration;
89    }
90    for(auto duration : timings_hw) {
91        avg_hw += duration;
92    }
93    for(auto duration : timings_hw_opt) {
94        avg_hw_opt += duration;
95    }
96    avg_sw = avg_sw / timings_sw.size();
97    avg_hw = avg_hw / timings_hw.size();
98    avg_hw_opt = avg_hw_opt / timings_hw.size();
99
100    std::vector<std::string> rows(0);
101    rows.push_back(std::to_string(test_size));
102    rows.push_back(cb::time2text(avg_sw));
103    rows.push_back(gib_per_sec(test_size, avg_sw));
104    rows.push_back(cb::time2text(avg_hw));
105    rows.push_back(gib_per_sec(test_size, avg_hw));
106    rows.push_back(get_ratio_string(avg_sw, avg_hw));
107    rows.push_back(cb::time2text(avg_hw_opt));
108    rows.push_back(gib_per_sec(test_size, avg_hw_opt));
109    rows.push_back(get_ratio_string(avg_hw, avg_hw_opt));
110    rows.push_back(get_ratio_string(avg_sw, avg_hw_opt));
111
112    for (size_t ii = 0; ii < column_heads.size(); ii++) {
113        std::string spacer(column_heads[ii].length() - rows[ii].length(), ' ');
114        std::cout << rows[ii] << spacer << ": ";
115    }
116    std::cout << std::endl;
117}
118
119void crc_bench_core(const uint8_t* buffer,
120                    size_t len,
121                    int iterations,
122                    crc32c_function crc32c_fn,
123                    DurationVector& timings) {
124    for (int i = 0; i < iterations; i++) {
125        const auto start = std::chrono::steady_clock::now();
126        crc32c_fn(buffer, len, 0);
127        const auto end = std::chrono::steady_clock::now();
128        timings.push_back((end - start) +
129                          std::chrono::steady_clock::duration(1));
130    }
131}
132
133void crc_bench(size_t len,
134               int iterations,
135               int unalignment) {
136    uint8_t* data = new uint8_t[len+unalignment];
137    std::mt19937 twister(static_cast<int>(len));
138    std::uniform_int_distribution<> dis(0, 0xff);
139    for (size_t data_index = 0; data_index < len; data_index++) {
140        uint8_t data_value = static_cast<uint8_t>(dis(twister));
141        data[data_index] = data_value;
142    }
143    DurationVector timings_sw, timings_hw, timings_hw_opt;
144    crc_bench_core(data+unalignment, len, iterations, crc32c_sw, timings_sw);
145    crc_bench_core(data+unalignment, len, iterations, crc32c_hw_1way, timings_hw);
146    crc_bench_core(data+unalignment, len, iterations, crc32c_hw, timings_hw_opt);
147    delete [] data;
148
149    crc_results(len, timings_sw, timings_hw, timings_hw_opt);
150
151}
152
153int main() {
154
155    // Print a notice if the clock duration is probably too big
156    // to measure the smaller tests. 20 seems about right from
157    // running on a variety of systems.
158    if (std::chrono::steady_clock::duration(1) > std::chrono::nanoseconds(20)) {
159        std::cout << "Note: The small tests maybe too fast to observe with "
160                  << "this system's clock. The clock duration "
161                  << "on this system is "
162                  << cb::time2text(std::chrono::steady_clock::duration(1))
163                  << std::endl;
164    }
165
166    crc_results_banner();
167
168    // test up to 8Mb
169    std::cout << "Power of 2 lengths." << std::endl;
170    for(size_t size = 32; size <= 8*(1024*1024); size = size * 2) {
171        crc_bench(size, 1000, 0);
172    }
173    std::cout << std::endl;
174
175    // Test some non power of 2 input sizes
176    std::cout << "Non-power of 2 lengths." << std::endl;
177    for(size_t size = 33; size <= 8*(1024*1024); size = size * 4) {
178        crc_bench(size, 1000, 0);
179    }
180    std::cout << std::endl;
181
182    // Test some inputs that are odd lengths and unaligned pointers
183    std::cout << "Unaligned buffer of odd lengths" << std::endl;
184    for(size_t size = 33; size <= 8*(1024*1024); size = size * 4) {
185        crc_bench(size % 2 == 0? size+1:size, 1000, 1);
186    }
187    return 0;
188}
189