Added more 128 bit calculations

This commit is contained in:
SChernykh 2022-11-15 22:20:54 +01:00
parent b3bce1651b
commit 488ed8e562
6 changed files with 317 additions and 51 deletions

View file

@ -109,6 +109,8 @@ constexpr uint8_t TX_EXTRA_MERGE_MINING_TAG = 3;
#ifdef _MSC_VER
#define umul128 _umul128
#define udiv128 _udiv128
FORCEINLINE uint64_t shiftleft128(uint64_t lo, uint64_t hi, uint64_t shift) { return __shiftleft128(lo, hi, static_cast<unsigned char>(shift)); }
FORCEINLINE uint64_t shiftright128(uint64_t lo, uint64_t hi, uint64_t shift) { return __shiftright128(lo, hi, static_cast<unsigned char>(shift)); }
#else
FORCEINLINE uint64_t umul128(uint64_t a, uint64_t b, uint64_t* hi)
{
@ -126,6 +128,9 @@ FORCEINLINE uint64_t udiv128(uint64_t hi, uint64_t lo, uint64_t divisor, uint64_
return result;
}
FORCEINLINE uint64_t shiftleft128(uint64_t lo, uint64_t hi, uint64_t shift) { return (hi << shift) | (lo >> (64 - shift)); }
FORCEINLINE uint64_t shiftright128(uint64_t lo, uint64_t hi, uint64_t shift) { return (hi << (64 - shift)) | (lo >> shift); }
#endif
template<typename T> constexpr FORCEINLINE T round_up(T a, size_t granularity) { return static_cast<T>(((a + (granularity - static_cast<size_t>(1))) / granularity) * granularity); }
@ -197,6 +202,8 @@ struct difficulty_type
return *this;
}
FORCEINLINE difficulty_type& operator+=(uint64_t b) { return operator+=(difficulty_type{ b, 0 }); }
FORCEINLINE difficulty_type& operator-=(const difficulty_type& b)
{
#ifdef _MSC_VER
@ -212,6 +219,8 @@ struct difficulty_type
return *this;
}
FORCEINLINE difficulty_type& operator-=(uint64_t b) { return operator-=(difficulty_type{ b, 0 }); }
FORCEINLINE difficulty_type& operator*=(const uint64_t b)
{
uint64_t t;
@ -232,6 +241,8 @@ struct difficulty_type
return *this;
}
difficulty_type& operator/=(difficulty_type b);
FORCEINLINE bool operator<(const difficulty_type& other) const
{
if (hi < other.hi) return true;
@ -279,20 +290,37 @@ struct difficulty_type
static_assert(sizeof(difficulty_type) == sizeof(uint64_t) * 2, "struct difficulty_type has invalid size, check your compiler options");
static_assert(std::is_standard_layout<difficulty_type>::value, "struct difficulty_type is not a POD, check your compiler options");
FORCEINLINE difficulty_type operator+(const difficulty_type& a, const difficulty_type& b)
template<typename T>
FORCEINLINE difficulty_type operator+(const difficulty_type& a, const T& b)
{
difficulty_type result = a;
result += b;
return result;
}
FORCEINLINE difficulty_type operator-(const difficulty_type& a, const difficulty_type& b)
template<typename T>
FORCEINLINE difficulty_type operator-(const difficulty_type& a, const T& b)
{
difficulty_type result = a;
result -= b;
return result;
}
FORCEINLINE difficulty_type operator*(const difficulty_type& a, uint64_t b)
{
difficulty_type result = a;
result *= b;
return result;
}
template<typename T>
FORCEINLINE difficulty_type operator/(const difficulty_type& a, const T& b)
{
difficulty_type result = a;
result /= b;
return result;
}
struct TxMempoolData
{
FORCEINLINE TxMempoolData() : id(), blob_size(0), weight(0), fee(0), time_received(0) {}

View file

@ -63,7 +63,6 @@ public:
// Create default loop here
uv_default_loop();
init_uv_threadpool();
uv_cond_init(&m_cond);
uv_mutex_init(&m_mutex);
@ -92,6 +91,8 @@ public:
if (!m_logFile.is_open()) {
LOGERR(0, "failed to open " << log_file_name);
}
init_uv_threadpool();
}
~Worker()
@ -160,7 +161,6 @@ private:
#endif
const uint32_t N = std::max(std::min(std::thread::hardware_concurrency(), 4U), 8U);
LOGINFO(4, "running " << N << " threads in the UV thread pool");
char buf[40] = {};
log::Stream s(buf);
@ -169,13 +169,13 @@ private:
int err = putenv(buf);
if (err != 0) {
err = errno;
LOGWARN(1, "Couldn't set UV thread pool size to " << N << " threads, putenv returned error " << err);
LOGWARN(0, "Couldn't set UV thread pool size to " << N << " threads, putenv returned error " << err);
}
static uv_work_t dummy;
err = uv_queue_work(uv_default_loop_checked(), &dummy, [](uv_work_t*) {}, nullptr);
if (err) {
LOGERR(1, "init_uv_threadpool: uv_queue_work failed, error " << uv_err_name(err));
LOGERR(0, "init_uv_threadpool: uv_queue_work failed, error " << uv_err_name(err));
}
}

View file

@ -325,7 +325,7 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares
uint64_t block_depth = 0;
const PoolBlock* cur = tip;
do {
MinerShare cur_share{ cur->m_difficulty.lo, &cur->m_minerWallet };
MinerShare cur_share{ cur->m_difficulty, &cur->m_minerWallet };
for (const hash& uncle_id : cur->m_uncles) {
auto it = m_blocksById.find(uncle_id);
@ -343,14 +343,9 @@ bool SideChain::get_shares(const PoolBlock* tip, std::vector<MinerShare>& shares
}
// Take some % of uncle's weight into this share
uint64_t product[2];
product[0] = umul128(uncle->m_difficulty.lo, m_unclePenalty, &product[1]);
uint64_t rem;
const uint64_t uncle_penalty = udiv128(product[1], product[0], 100, &rem);
const difficulty_type uncle_penalty = uncle->m_difficulty * m_unclePenalty / 100;
cur_share.m_weight += uncle_penalty;
shares.emplace_back(uncle->m_difficulty.lo - uncle_penalty, &uncle->m_minerWallet);
shares.emplace_back(uncle->m_difficulty - uncle_penalty, &uncle->m_minerWallet);
}
shares.push_back(cur_share);
@ -1092,9 +1087,9 @@ bool SideChain::split_reward(uint64_t reward, const std::vector<MinerShare>& sha
{
const size_t num_shares = shares.size();
const uint64_t total_weight = std::accumulate(shares.begin(), shares.end(), 0ULL, [](uint64_t a, const MinerShare& b) { return a + b.m_weight; });
const difficulty_type total_weight = std::accumulate(shares.begin(), shares.end(), difficulty_type(), [](const difficulty_type& a, const MinerShare& b) { return a + b.m_weight; });
if (total_weight == 0) {
if (total_weight.empty()) {
LOGERR(1, "total_weight is 0. Check the code!");
return false;
}
@ -1103,18 +1098,14 @@ bool SideChain::split_reward(uint64_t reward, const std::vector<MinerShare>& sha
rewards.reserve(num_shares);
// Each miner gets a proportional fraction of the block reward
uint64_t w = 0;
difficulty_type w;
uint64_t reward_given = 0;
for (uint64_t i = 0; i < num_shares; ++i) {
w += shares[i].m_weight;
uint64_t hi;
const uint64_t lo = umul128(w, reward, &hi);
uint64_t rem;
const uint64_t next_value = udiv128(hi, lo, total_weight, &rem);
rewards.emplace_back(next_value - reward_given);
reward_given = next_value;
const difficulty_type next_value = w * reward / total_weight;
rewards.emplace_back(next_value.lo - reward_given);
reward_given = next_value.lo;
}
// Double check that we gave out the exact amount
@ -1209,9 +1200,7 @@ bool SideChain::get_difficulty(const PoolBlock* tip, std::vector<DifficultyData>
}
}
curDifficulty = diff2 - diff1;
curDifficulty *= m_targetBlockTime;
curDifficulty /= delta_t;
curDifficulty = (diff2 - diff1) * m_targetBlockTime / delta_t;
if (curDifficulty < m_minDifficulty) {
curDifficulty = m_minDifficulty;

View file

@ -29,10 +29,10 @@ class P2PServer;
struct MinerShare
{
FORCEINLINE MinerShare() : m_weight(0), m_wallet(nullptr) {}
FORCEINLINE MinerShare(uint64_t w, const Wallet* x) : m_weight(w), m_wallet(x) {}
FORCEINLINE MinerShare() : m_weight(), m_wallet(nullptr) {}
FORCEINLINE MinerShare(const difficulty_type& w, const Wallet* x) : m_weight(w), m_wallet(x) {}
uint64_t m_weight;
difficulty_type m_weight;
const Wallet* m_wallet;
};

View file

@ -75,6 +75,54 @@ void make_thread_background()
#endif
}
NOINLINE difficulty_type& difficulty_type::operator/=(difficulty_type b)
{
if (*this < b) {
lo = 0;
hi = 0;
return *this;
}
if (*this - b < b) {
lo = 1;
hi = 0;
return *this;
}
if (b.hi == 0) {
return operator/=(b.lo);
}
const uint64_t shift = bsr(b.hi) + 1;
const uint64_t divisor = shiftleft128(b.lo, b.hi, 64 - shift);
uint64_t t;
if (hi < divisor) {
uint64_t r;
t = udiv128(hi, lo, divisor, &r) >> shift;
}
else {
uint64_t r;
t = shiftright128(udiv128(hi - divisor, lo, divisor, &r), 1, shift);
}
difficulty_type product;
product.lo = umul128(b.lo, t, &product.hi);
uint64_t t1, t2;
t1 = umul128(b.hi, t, &t2);
product.hi += t1;
if (t2 || (product.hi < t1) || (*this < product)) {
--t;
}
lo = t;
hi = 0;
return *this;
}
NOINLINE bool difficulty_type::check_pow(const hash& pow_hash) const
{
const uint64_t* a = reinterpret_cast<const uint64_t*>(pow_hash.h);

View file

@ -24,6 +24,8 @@
namespace p2pool {
static const difficulty_type max_diff{ std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max() };
TEST(difficulty_type, constructors)
{
difficulty_type diff;
@ -56,10 +58,7 @@ TEST(difficulty_type, target)
}
// diff = max
{
difficulty_type d(std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max());
ASSERT_EQ(d.target(), 1);
}
ASSERT_EQ(max_diff.target(), 1);
// diff = 2^32
{
@ -128,54 +127,60 @@ TEST(difficulty_type, mul_div)
{
auto check = [](const difficulty_type& a, uint64_t b, const difficulty_type& product)
{
difficulty_type result = a;
ASSERT_EQ(a * b, product);
difficulty_type result = a;
result *= b;
ASSERT_EQ(result, product);
if (b) {
result /= b;
ASSERT_EQ(result, a);
ASSERT_EQ(result / b, a);
difficulty_type tmp = result;
tmp /= difficulty_type(b, 0);
ASSERT_EQ(tmp, a);
difficulty_type tmp2 = result;
tmp2 /= b;
ASSERT_EQ(tmp2, a);
}
};
const difficulty_type max_diff(std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max());
// (2^128 - 1) * 0 = 0
check(max_diff, 0, difficulty_type(0, 0));
check(max_diff, 0, { 0, 0 });
// (2^128 - 1) * 1 = 2^128 - 1
check(max_diff, 1, max_diff);
// 5057672949897463733145855 * 67280421310721 = 2^128 - 1
check(difficulty_type(18446744073709277439ull, 274176ull), 67280421310721ull, max_diff);
check({ 18446744073709277439ull, 274176ull }, 67280421310721ull, max_diff);
// 10^19 * 10 = 10^20
check(difficulty_type(10000000000000000000ull, 0), 10, difficulty_type(7766279631452241920ull, 5));
check({ 10000000000000000000ull, 0 }, 10, { 7766279631452241920ull, 5 });
// 10^20 * 10 = 10^21
check(difficulty_type(7766279631452241920ull, 5), 10, difficulty_type(3875820019684212736ull, 54));
check({ 7766279631452241920ull, 5 }, 10, { 3875820019684212736ull, 54 });
// 0 * (2^64 - 1) = 0
check(difficulty_type(0, 0), std::numeric_limits<uint64_t>::max(), difficulty_type(0, 0));
check({ 0, 0 }, std::numeric_limits<uint64_t>::max(), { 0, 0 });
// 1 * (2^64 - 1) = 2^64 - 1
check(difficulty_type(1, 0), std::numeric_limits<uint64_t>::max(), difficulty_type(std::numeric_limits<uint64_t>::max(), 0));
check({ 1, 0 }, std::numeric_limits<uint64_t>::max(), { std::numeric_limits<uint64_t>::max(), 0 });
// 2^64 * (2^64 - 1) = 2^128 - 2^64
check(difficulty_type(0, 1), std::numeric_limits<uint64_t>::max(), difficulty_type(0, std::numeric_limits<uint64_t>::max()));
check({ 0, 1 }, std::numeric_limits<uint64_t>::max(), { 0, std::numeric_limits<uint64_t>::max() });
// (2^64 + 1) * (2^64 - 1) = 2^128 - 1
check(difficulty_type(1, 1), std::numeric_limits<uint64_t>::max(), max_diff);
check({ 1, 1 }, std::numeric_limits<uint64_t>::max(), max_diff);
// 2753074036095 * 6700417 = 2^64 - 1
check(difficulty_type(2753074036095ull, 0), 6700417, difficulty_type(std::numeric_limits<uint64_t>::max(), 0));
check({ 2753074036095ull, 0 }, 6700417, { std::numeric_limits<uint64_t>::max(), 0 });
// 2^32 * 2^32 = 2^64
check(difficulty_type(4294967296ull, 0), 4294967296ull, difficulty_type(0, 1));
check({ 4294967296ull, 0 }, 4294967296ull, { 0, 1 });
// 274177 * 67280421310721 = 2^64 + 1
check(difficulty_type(274177, 0), 67280421310721ull, difficulty_type(1, 1));
check({ 274177, 0 }, 67280421310721ull, { 1, 1 });
// Powers of 2
{
@ -210,7 +215,203 @@ TEST(difficulty_type, mul_div)
}
// No carry
check(difficulty_type(123, 456), 789, difficulty_type(97047, 359784));
check({ 123, 456 }, 789, { 97047, 359784 });
}
static NOINLINE difficulty_type div128_ref(difficulty_type a, difficulty_type b)
{
difficulty_type result{};
while (a >= b) {
difficulty_type t = b;
difficulty_type q{ 1, 0 };
while (a - t >= t) {
t += t;
q += q;
}
a -= t;
result += q;
}
return result;
}
TEST(difficulty_type, div128)
{
auto check = [](difficulty_type a, difficulty_type b, difficulty_type result)
{
ASSERT_EQ(div128_ref(a, b), result);
ASSERT_EQ(a / b, result);
a /= b;
ASSERT_EQ(a, result);
};
// (2^128 - 1) / (2^128 - 1) = 1
check(max_diff, max_diff, { 1, 0 });
// (2^128 - 1) / (2^64 - 1) = 2^64 + 1
check(max_diff, { std::numeric_limits<uint64_t>::max(), 0 }, { 1, 1 });
// (2^128 - 1) / 2^64 = 2^64 - 1
check(max_diff, { 0, 1 }, { std::numeric_limits<uint64_t>::max(), 0 });
// (2^128 - 1) / (2^64 + 1) = 2^64 - 1
check(max_diff, { 1, 1 }, { std::numeric_limits<uint64_t>::max(), 0 });
// (2^128 - 1) / 8100430714362380904069067128193 = 42007935
check(max_diff, { 439125228929, 439125228929 }, { 42007935, 0 });
// (2^128 - 2^64) / (2^64 + 1) = 2^64 - 2
check({ 0, std::numeric_limits<uint64_t>::max() }, { 1, 1 }, { std::numeric_limits<uint64_t>::max() - 1, 0 });
// (2^128 - 2^64) / 2^64 = 2^64 - 1
check({ 0, std::numeric_limits<uint64_t>::max() }, { 0, 1 }, { std::numeric_limits<uint64_t>::max(), 0 });
// (2^128 - 2^64) / (2^64 - 1) = 2^64
check({ 0, std::numeric_limits<uint64_t>::max() }, { std::numeric_limits<uint64_t>::max(), 0 }, { 0, 1 });
{
difficulty_type a = max_diff - 4;
// (2^128 - 5) / 2002733033099709041094789607565039 = 169909
check(a, { 7565587230673184495, 108568375269759 }, { 169909, 0 });
// (2^128 - 5) / 2002733033099709041094789607565040 = 169908
check(a, { 7565587230673184496, 108568375269759 }, { 169908, 0 });
a -= 1;
// (2^128 - 6) / 2002733033099709041094789607565039 = 169908
check(a, { 7565587230673184495, 108568375269759 }, { 169908, 0 });
// (2^128 - 6) / 2002733033099709041094789607565038 = 169909
check(a, { 7565587230673184494, 108568375269759 }, { 169909, 0 });
}
// Powers of 2
for (difficulty_type i{ 1, 0 }, j = max_diff; !i.empty(); i += i, j /= 2) {
check(max_diff, i, j);
}
// Trivial tests
check({ 0, 3 }, { 0, 1 }, { 3, 0 });
check({ 0, 3 }, { 1, 1 }, { 2, 0 });
check({ 123 * 4 - 1, 456 * 4 }, { 123, 456 }, { 3, 0 });
check({ 123 * 4, 456 * 4 }, { 123, 456 }, { 4, 0 });
// Exhaustive tests (top 8 bits of each number)
for (uint64_t i = 1; i < 256; ++i) {
for (uint64_t j = 1; j < 256; ++j) {
const difficulty_type a{ 0, i << 56 };
const difficulty_type b{ 0, j << 56 };
{
difficulty_type t = a;
t /= b;
ASSERT_EQ(t.lo, i / j);
ASSERT_EQ(t.hi, 0);
}
}
}
// Bit patterns
std::vector<difficulty_type> patterns;
// 2^N-1, 2^N, 2^N+1
for (uint64_t i = 0; i < 128; ++i) {
difficulty_type t;
reinterpret_cast<uint64_t*>(&t)[i / 64] |= 1ull << (i % 64);
patterns.emplace_back(t - 1);
patterns.emplace_back(t);
patterns.emplace_back(t + 1);
}
// 2^N+2^M, 2^N-2^M
bool check_bits[128] = {};
for (uint64_t i = 64 - 4; i < 64 + 4; ++i) {
check_bits[i] = true;
}
for (uint64_t i = 128 - 8; i < 128; ++i) {
check_bits[i] = true;
}
for (uint64_t i = 0; i < 128; ++i) {
if (!check_bits[i]) {
continue;
}
difficulty_type t1;
reinterpret_cast<uint64_t*>(&t1)[i / 64] = 1ull << (i % 64);
for (uint64_t j = i + 1; j < 128; ++j) {
if (!check_bits[j]) {
continue;
}
difficulty_type t2;
reinterpret_cast<uint64_t*>(&t2)[j / 64] = 1ull << (j % 64);
patterns.emplace_back(t2 + t1);
patterns.emplace_back(t2 - t1);
}
}
// All previous patterns, but ~X
for (size_t i = 0, n = patterns.size(); i < n; ++i) {
patterns.emplace_back(~patterns[i].lo, ~patterns[i].hi);
}
std::sort(patterns.begin(), patterns.end());
patterns.erase(std::unique(patterns.begin(), patterns.end()), patterns.end());
// remove 0
patterns.erase(patterns.begin());
for (size_t i = 0, n = patterns.size(); i < n; ++i) {
const difficulty_type& a = patterns[i];
for (size_t j = i + 1; j < n; ++j) {
const difficulty_type& b = patterns[j];
ASSERT_EQ(div128_ref(b, a), b / a);
}
}
// Random tests with fixed seed
std::mt19937_64 r(0);
for (uint64_t i = 0; i < 10000000; ++i) {
// Random number of bits [1, 63]
const uint64_t N = (r() % 63) + 1;
// Random multiplier [1, 2^N - 1]
uint64_t k;
do {
k = r() & ((1ull << N) - 1);
} while (k == 0);
uint64_t t;
const uint64_t max_a = udiv128(1, 0, k + 1, &t);
// Random number [2^64, 2^128 / (k + 1)]
difficulty_type a{ r(), 0 };
do {
a.hi = r() % max_a;
} while (a.hi == 0);
difficulty_type b1 = a * k;
difficulty_type b2 = b1 - 1;
difficulty_type b3 = b1 + a;
difficulty_type b4 = b3 - 1;
b1 /= a;
ASSERT_EQ(b1.lo, k);
ASSERT_EQ(b1.hi, 0);
b2 /= a;
ASSERT_EQ(b2.lo, k - 1);
ASSERT_EQ(b2.hi, 0);
b3 /= a;
ASSERT_EQ(b3.lo, k + 1);
ASSERT_EQ(b3.hi, 0);
b4 /= a;
ASSERT_EQ(b4.lo, k);
ASSERT_EQ(b4.hi, 0);
}
}
TEST(difficulty_type, compare)