1 // ethash: C/C++ implementation of Ethash, the Ethereum Proof of Work algorithm.
2 // Copyright 2018 Pawel Bylica.
3 // Licensed under the Apache License, Version 2.0. See the LICENSE file.
4
5 #include <ethash/ethash.hpp>
6
7 #include <atomic>
8 #include <chrono>
9 #include <future>
10 #include <iomanip>
11 #include <iostream>
12 #include <string>
13 #include <unordered_map>
14 #include <vector>
15
16
17 using namespace std::chrono;
18 using timer = std::chrono::steady_clock;
19
20 namespace
21 {
22 class ethash_interface
23 {
24 public:
25 virtual ~ethash_interface() noexcept = default;
26
27 virtual void search(const ethash::hash256& header_hash, uint64_t nonce, size_t iterations) const
28 noexcept = 0;
29 };
30
31 class ethash_light : public ethash_interface
32 {
33 const ethash::epoch_context& context;
34
35 public:
ethash_light(int epoch_number)36 explicit ethash_light(int epoch_number)
37 : context(ethash::get_global_epoch_context(epoch_number))
38 {}
39
search(const ethash::hash256 & header_hash,uint64_t nonce,size_t iterations) const40 void search(const ethash::hash256& header_hash, uint64_t nonce, size_t iterations) const
41 noexcept override
42 {
43 ethash::search_light(context, header_hash, {}, nonce, iterations);
44 }
45 };
46
47 class ethash_full : public ethash_interface
48 {
49 const ethash::epoch_context_full& context;
50
51 public:
ethash_full(int epoch_number)52 explicit ethash_full(int epoch_number)
53 : context(ethash::get_global_epoch_context_full(epoch_number))
54 {}
55
search(const ethash::hash256 & header_hash,uint64_t nonce,size_t iterations) const56 void search(const ethash::hash256& header_hash, uint64_t nonce, size_t iterations) const
57 noexcept override
58 {
59 ethash::search(context, header_hash, {}, nonce, iterations);
60 }
61 };
62
63
64 std::atomic<int> shared_block_number{0};
65 std::atomic<int> num_hashes{0};
66
worker(bool light,const ethash::hash256 & header_hash,uint64_t start_nonce,int batch_size)67 void worker(bool light, const ethash::hash256& header_hash, uint64_t start_nonce, int batch_size)
68 {
69 int current_epoch = -1;
70 std::unique_ptr<ethash_interface> ei;
71 uint64_t i = 0;
72 size_t w = static_cast<size_t>(batch_size);
73 while (true)
74 {
75 int block_number = shared_block_number.load(std::memory_order_relaxed);
76 if (block_number < 0)
77 break;
78
79 int e = ethash::get_epoch_number(block_number);
80
81 if (current_epoch != e)
82 {
83 ei.reset(
84 light ? static_cast<ethash_interface*>(new ethash_light{e}) : new ethash_full{e});
85 current_epoch = e;
86 }
87
88 ei->search(header_hash, start_nonce + i, w);
89 num_hashes.fetch_add(batch_size, std::memory_order_relaxed);
90 i += w;
91 }
92 }
93 } // namespace
94
main(int argc,const char * argv[])95 int main(int argc, const char* argv[])
96 {
97 int num_blocks = 10;
98 int start_block_number = 0;
99 int block_time = 6;
100 int work_size = 100;
101 int num_threads = static_cast<int>(std::thread::hardware_concurrency());
102 uint64_t start_nonce = 0;
103 bool light = false;
104
105 for (int i = 0; i < argc; ++i)
106 {
107 const std::string arg{argv[i]};
108
109 if (arg == "--light")
110 light = true;
111 else if (arg == "-i" && i + 1 < argc)
112 num_blocks = std::stoi(argv[++i]);
113 else if (arg == "-b" && i + 1 < argc)
114 start_block_number = std::stoi(argv[++i]);
115 else if (arg == "-t" && i + 1 < argc)
116 num_threads = std::stoi(argv[++i]);
117 else if (arg == "-n" && i + 1 < argc)
118 start_nonce = std::stoul(argv[++i]);
119 }
120
121 auto flags = std::cout.flags();
122 std::cout << std::fixed << std::setprecision(2);
123
124 // clang-format off
125 std::cout << "Fakeminer Benchmark\n\nParameters:"
126 << "\n dataset: " << (light ? "light" : "full")
127 << "\n threads: " << num_threads
128 << "\n blocks: " << num_blocks
129 << "\n block time: " << block_time
130 << "\n batch size: " << work_size
131 << "\n start nonce: " << start_nonce
132 << "\n\n";
133 // clang-format on
134
135 const uint64_t divisor = static_cast<uint64_t>(num_threads);
136 const uint64_t nonce_space_per_thread = std::numeric_limits<uint64_t>::max() / divisor;
137
138 const ethash::hash256 header_hash{};
139
140 shared_block_number.store(start_block_number, std::memory_order_relaxed);
141 std::vector<std::future<void>> futures;
142
143 for (int t = 0; t < num_threads; ++t)
144 {
145 futures.emplace_back(
146 std::async(std::launch::async, worker, light, header_hash, start_nonce, work_size));
147 start_nonce += nonce_space_per_thread;
148 }
149
150 std::cout << "Progress:\n"
151 << " |----- hashrate -----| |----- bandwidth -----|\n"
152 << " epoch block current average current average\n";
153
154 int all_hashes = 0;
155 auto start_time = timer::now();
156 auto time = start_time;
157 static constexpr int khps_mbps_ratio =
158 ethash::num_dataset_accesses * ethash::full_dataset_item_size / 1024;
159
160 double current_duration = 0;
161 double all_duration = 0;
162 double current_khps = 0;
163 double average_khps = 0;
164 double current_bandwidth = 0;
165 double average_bandwidth = 0;
166
167 const milliseconds block_time_ms{block_time * 1000};
168 milliseconds sleep_time = block_time_ms;
169 const int end_block_number = start_block_number + num_blocks;
170 for (int block_number = start_block_number; block_number < end_block_number; ++block_number)
171 {
172 std::this_thread::sleep_for(sleep_time);
173 int current_hashes = num_hashes.exchange(0, std::memory_order_relaxed);
174 all_hashes += current_hashes;
175
176 auto now = timer::now();
177 current_duration = double(duration_cast<milliseconds>(now - time).count());
178 all_duration = double(duration_cast<milliseconds>(now - start_time).count());
179 time = now;
180
181 shared_block_number.store(block_number + 1, std::memory_order_relaxed);
182
183 int e = ethash::get_epoch_number(block_number);
184
185 current_khps = double(current_hashes) / current_duration;
186 average_khps = double(all_hashes) / all_duration;
187 current_bandwidth = double(current_hashes * khps_mbps_ratio) / 1024 / current_duration;
188 average_bandwidth = double(all_hashes * khps_mbps_ratio) / 1024 / all_duration;
189
190 std::cout << std::setw(7) << e << std::setw(9) << block_number << std::setw(10)
191 << current_khps << " kh/s" << std::setw(9) << average_khps << " kh/s"
192 << std::setw(10) << current_bandwidth << " GiB/s" << std::setw(8)
193 << average_bandwidth << " GiB/s\n";
194
195 sleep_time = block_time_ms - duration_cast<milliseconds>(timer::now() - now);
196 }
197
198 shared_block_number.store(-1, std::memory_order_relaxed);
199 for (auto& future : futures)
200 future.wait();
201
202 auto total_seconds = all_duration / 1000;
203
204 std::cout << "\nSummary:\n time: " << std::setw(7) << total_seconds
205 << " s\n latest hashrate: " << std::setw(7) << current_khps
206 << " kh/s\n average hashrate: " << std::setw(7) << average_khps
207 << " kh/s\n latest memory bandwitdh: " << std::setw(7) << current_bandwidth
208 << " GiB/s\n average memory bandwitdh: " << std::setw(7) << average_bandwidth
209 << " GiB/s\n";
210
211 std::cout.flags(flags);
212 return 0;
213 }
214