Structure Packing Optimizations in CTransaction and CTxMemPoolEntry #8330

Open
wants to merge 1 commit into
from

Conversation

Projects
None yet
6 participants
Contributor

JeremyRubin commented Jul 11, 2016

These commits revise the layout of a few key classes to eliminate padding, eliminating useless memory overhead.

CTxMemPoolEntry is decreased to 184 bytes from 192. CTransaction is decreased to 112 bytes from 120.

Furthermore, it is clarified in CTxMempoolEntry where the extra space is (2 bytes) for future overhead-free use.

The introduction of CTxMemPoolEntryFlags is used to bit-pack the bool flags, making room for 6 more future-flags. I have not performance tested this, but it should not be much. It does not save any space at the moment due to padding, but if more bools are added it would compared to naive.

To finish this work, the following should be done: profiling of access patterns to class fields to guarantee cache-line co-residency, cross architecture padding optimizations.

@jonasschnelli jonasschnelli commented on an outdated diff Jul 12, 2016

src/primitives/transaction.h
@@ -433,15 +433,14 @@ class CTransaction
void UpdateHash() const;
};
-
@jonasschnelli

jonasschnelli Jul 12, 2016

Member

nit: we mostly have newlines between classes.

@jonasschnelli jonasschnelli and 2 others commented on an outdated diff Jul 12, 2016

src/txmempool.h
+ // Space that is typically used as padding. Commented out so that it doesn't cause extra memory use on different architectures.
+ // char PADDING_FREE_SPACE[2];
+
+ // Bool flags
+ struct CTxMemPoolEntryFlags {
+ int UNUSED_BITS : 6;
@jonasschnelli

jonasschnelli Jul 12, 2016

Member

What's the purpose of UNUSED_BITS?

Maybe a uint32_t bitmap would be the better choice here?

@JeremyRubin

JeremyRubin Jul 12, 2016

Contributor

There's no purpose, just to demarcate that these bits are available for future programmers. A uint32_t would be 4 bytes whereas this struct should be 1 byte.

@jonasschnelli

jonasschnelli Jul 12, 2016

Member

Sorry.. overlooked that you are using a c++ bitfield. But I guess the struct will still consume 4 bytes.

@sipa

sipa via email Jul 12, 2016

Owner
@JeremyRubin

JeremyRubin Jul 12, 2016

Contributor

@sipa are you sure of the alignment? On my system it is 8 bytes, which leaves no padding for the CTxMemPoolEntry. Perhaps you're on OSX where size_t is 4 bytes not 8 (I didn't look at OSX).

@sipa

sipa Aug 25, 2016

Owner

@JeremyRubin size_t is the size of a pointer, which is 32 bits on 32 bits architectures and 64 bits on 64 bits architectures. You're right that the overall alignment is 8 bytes (on my architecture at least).

It seems the flags field becomes 4 bytes anyway, so there is no padding afterwards.

Member

jonasschnelli commented Jul 12, 2016

Concept ACK

Owner

sipa commented Jul 12, 2016

No, I'm not. I haven't actually tried. But I assume the int before the bitfield is 4-aligned, which would make the end of the bitfield byte be at a multiple of 4 plus 1?

Member

gmaxwell commented Dec 5, 2016

Needs rebase. Concept ACK, on the placeholder bitfield, perhaps better to make that a comment?

Contributor

JeremyRubin commented Jan 10, 2017 edited

rebased and addressed feedback

JeremyRubin changed the title from WIP: Structure Packing Optimizations in CTransaction and CTxMemPoolEntry to Structure Packing Optimizations in CTransaction and CTxMemPoolEntry Mar 27, 2017

Contributor

JeremyRubin commented Mar 27, 2017

Rebased! (removing priority conflicted with this)

src/txmempool.h
- bool spendsCoinbase; //!< keep track of transactions that spend a coinbase
+ struct CTxMemPoolEntryFlags {
+ //int UNUSED_BITS : 7;
+ bool spendsCoinbase : 1; //!< keep track of transactions that spend a coinbase
@ryanofsky

ryanofsky Mar 27, 2017

Contributor

Kind of a lonely bitfield now with just one bool. But maybe it will have some more flags later.

@JeremyRubin

JeremyRubin Mar 28, 2017

Contributor

yeah, hopefully inspires whoever adds a flag in the future...

src/txmempool.h
@@ -94,7 +89,17 @@ class CTxMemPoolEntry
uint64_t nSizeWithAncestors;
CAmount nModFeesWithAncestors;
int64_t nSigOpCostWithAncestors;
+ size_t nTxWeight; //!< ... and avoid recomputing tx weight (also used for GetTxSize())
+ size_t nModSize; //!< ... and modified size for priority
@ryanofsky

ryanofsky Mar 27, 2017

Contributor

In commit "Re-pack the CTxMempoolEntry to be closer to optimally packed":

This isn't used, guessing bad rebase.

@JeremyRubin

JeremyRubin Mar 28, 2017

Contributor

yeah... hmm :/

Will fix.

src/txmempool.h
+ // Bool flags
+ bool hadNoDependencies; //!< Not dependent on any other txs when it entered the mempool
@ryanofsky

ryanofsky Mar 27, 2017

Contributor

In commit "Re-pack the CTxMempoolEntry to be closer to optimally packed":

Also unused.

@@ -76,9 +76,12 @@ uint256 CTransaction::GetWitnessHash() const
}
/* For backward compatibility, the hash is initialized to 0. TODO: remove the need for this default constructor entirely. */
-CTransaction::CTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0), hash() {}
-CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime), hash(ComputeHash()) {}
@ryanofsky

ryanofsky Mar 27, 2017

Contributor

In commit "Reorder C{,Mutable}Transaction for better packing":

Seems like you could remove ComputeHash now, it doesn't appear to be called anywhere else.

src/txmempool.cpp
- tx(_tx), nFee(_nFee), nTime(_nTime), entryHeight(_entryHeight),
- spendsCoinbase(_spendsCoinbase), sigOpCost(_sigOpsCost), lockPoints(lp)
+ bool _spendsCoinbase, int64_t _sigOpsCost, LockPoints lp):
+lockPoints(lp), tx(_tx), nFee(_nFee)
@ryanofsky

ryanofsky Mar 28, 2017

Contributor

In commit "Re-pack the CTxMempoolEntry to be closer to optimally packed"

Unusual indentation here, maybe reformat.

src/txmempool.h
+ //int UNUSED_BITS : 7;
+ bool spendsCoinbase : 1; //!< keep track of transactions that spend a coinbase
+ };
+ CTxMemPoolEntryFlags flags;
@TheBlueMatt

TheBlueMatt Jul 11, 2017

Contributor

Ehh, why not just have the single bool spendsCoinbase : 1 here instead of making a struct for it?

@JeremyRubin

JeremyRubin Jul 12, 2017

Contributor

It's somewhat vestigial; iirc when I originally wrote this patch last year there was another bool that got removed in a later PR.

The only real remaining reason is to make the code "self documenting" so that when/if someone later adds another flag to mempool (or even adds the flag back), they are encouraged to dtrt rather than adding more memory bloat.

@TheBlueMatt

TheBlueMatt Jul 12, 2017

Contributor

Is there harm in having
bool spendsCoinbase : 1;
bool newFlags : 1;
instead of encapsulating the booleans in a flags struct?

@JeremyRubin

JeremyRubin Jul 12, 2017

Contributor

Is that valid c++? If so, o_0

@sipa

sipa Jul 12, 2017

Owner

In what way is that different from what you're doing now?

Owner

sipa commented Jul 12, 2017 edited

This reduces CTransaction from 96 bytes to 88 bytes for me (great), but increases CTxMemPoolEntry from 160 bytes to 168 bytes (not so great). NACK as long as that is the case.

Contributor

JeremyRubin commented Jul 13, 2017

@sipa I rewrote this PR to repack the CTransaction versions via a minimal patch.

I'm not sure what was going on with the CTxMemPoolEntry version, I just think it became stale. At the time I wrote the patch initially they were 192 bytes, so 160 bytes/entry is a good improvement anyways (perhaps from removing priority).

Owner

sipa commented Jul 13, 2017

utACK 37495e0. Verified with printf("%i\n", sizeof(CTransaction)) before and after that this indeed reduces the size of the type.

Contributor

TheBlueMatt commented Jul 14, 2017

utACK 37495e0

@ryanofsky

utACK 37495e0. Should update outdated PR description though to avoid misleading text in git history.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment