-
-
Notifications
You must be signed in to change notification settings - Fork 747
Add AlignedBlockList #6410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add AlignedBlockList #6410
Conversation
|
Thanks for your pull request and interest in making D better, @jercaianu! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please see CONTRIBUTING.md for more information. If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment. Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub fetch digger
dub run digger -- build "master + phobos#6410" |
wilzbach
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A quick initial feedback - mostly on the style. This is not a review.
I think you could save a lot of code if you would use a custom, local atomicOp - maybe this can even be done with alias this'ing the respective integral type?
| @@ -0,0 +1,629 @@ | |||
| // Written in the D programming language. | |||
| /** | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please provide a description here.
| @@ -0,0 +1,629 @@ | |||
| // Written in the D programming language. | |||
| /** | |||
| Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/_aligned_block_list.d) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preventing Ddoc's auto-hightlighting is no longer required - dlang/dlang.org#2307
| static if (isShared) | ||
| SpinLock lock = SpinLock(SpinLock.Contention.brief); | ||
|
|
||
| private void moveToFront(AlignedBlockNode *tmp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document
| } | ||
| } | ||
|
|
||
| AlignedBlockNode *root; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DStyle
| enum maxNodes = 50; | ||
|
|
||
| static if (isShared) | ||
| SpinLock lock = SpinLock(SpinLock.Contention.brief); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indent (DStyle)
|
|
||
| void fun() | ||
| { | ||
| auto rnd = Random(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. Use a fixed seed and a fixed engine, everything else might lead to random coverage changes.
| a.allocatorForSize!512.parent = &pageAlloc; | ||
| a.allocatorForSize!256.parent = &pageAlloc; | ||
|
|
||
| auto rnd = Random(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seed + fix the generator
| Although allocations are in theory served in linear searching time, `deallocate` calls take | ||
| $(BIGOH 1) time, by using aligned allocations. `ParentAllocator` must implement `alignedAllocate` | ||
| and it must be able to allocate `theAlignment` bytes at the same alignment. Each aligned allocation | ||
| done by `ParentAllocator` will contain metadata for an `Allocator`, followed by its payload. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a ddoc Params section.
| size_t goodAllocSize(const size_t n) | ||
| { | ||
| Allocator a = null; | ||
| return a.goodAllocSize(n); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this segfault?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If goodAllocSize does not use internal allocator state (and it shouldn't) it won't segfault.
Currently, this would work with all allocators in experimental
| static if (isShared) | ||
| { | ||
| atomicOp!"+="(tmp.bytesUsed, n); | ||
| lock.lock(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You never unlock this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit above I have a scope(exit) lock.unlock()
| import core.internal.spinlock : SpinLock; | ||
|
|
||
| private: | ||
| struct AlignedBlockNode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static struct?
|
@wilzbach Thanks for comments |
090e429 to
2f9396d
Compare
wilzbach
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import std.std doesn't work.
| { | ||
| import std.experimental.allocator.building_blocks.region : SharedRegion; | ||
| import std.experimental.allocator.building_blocks.ascending_page_allocator : SharedAscendingPageAllocator; | ||
| import std.std.experimental.allocator.building_blocks.null_allocator : NullAllocator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std/experimental/allocator/building_blocks/aligned_block_list.d(512): Error: module null_allocator is in file 'std/std/experimental/allocator/building_blocks/null_allocator.d' which cannot be read
import path[0] = ../druntime/import
posix.mak:350: recipe for target 'generated/linux/debug/32/unittest/std/experimental/allocator/building_blocks/aligned_block_list.o' failed
make[1]: *** [generated/linux/debug/32/unittest/std/experimental/allocator/building_blocks/aligned_block_list.o] Error 1
-> You have a std.std
5fa6a6b to
478ecd5
Compare
| int numNodes; | ||
|
|
||
| // If the numNodes exceeds this limit, we will start deallocating nodes | ||
| enum maxNodes = 50; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious as to where this number came from. Was it just a gut feeling? Did you make a decision to bias towards "probably not too high" or "probably not too low"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depending on what the caller passes, maxNodes should be different.
The deallocation of the blocks provided by ParentAllocator happen lazily inside allocate.
I didn't want to delete nodes too often, but also I didn't want to have high memory usage so I hardcoded something for my tests.
I'll investigate here a bit more to see if I can get rid of this, or maybe pass it as a parameter somehow.
Thanks!
| auto next = tmp.next; | ||
| if (tmp.prev) tmp.prev.next = tmp.next; | ||
| if (tmp.next) tmp.next.prev = tmp.prev; | ||
| assert(parent.deallocate((cast(void*) tmp)[0 .. alignment])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In release mode assertions are omitted and this code won't be run.
Example: https://run.dlang.io/is/m8gzG3
import std.stdio;
auto foo(ref int x)
{
return x &= 1;
}
void main(string[] args)
{
int x = 255;
assert(foo(x));
writeln(x);
}This prints 1 when run with no compiler arguments but prints 255 with -release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, good catch!
| static if (stateSize!ParentAllocator) ParentAllocator parent; | ||
| else alias parent = ParentAllocator.instance; | ||
|
|
||
| enum ulong alignment = theAlignment; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how this could be correct if theAlignment is the size of each node and memory is allocated at an offset from the start of the node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the metadata for each node and the payload are adjacent in memory, I considered the alignment to be for the whole block (metadata + payload).
Inside the payload, an Allocator can manage the allocations with its own alignment.
Edit: nevermind I see the problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the same time, I think it's not great for the caller to expect each allocation to have an alignment, but we silently add some padding before.
I think it's better for the alignment to be (metadata + payload), but clearly specify that and expose the length of the metadata.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because other allocators in std.experiment.allocator use alignment to specifically mean the alignment of memory returned by allocate, use a different name here and everything will be fine. Otherwise, templated code that uses design-by-introspection can leap to a wrong conclusion -- the compiler doesn't read the DDOC, so a comment will not prevent that!
| done by `ParentAllocator` will contain metadata for an `Allocator`, followed by its payload. | ||
| Params: | ||
| Allocator = the allocator which is used to manage each node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation does an insufficient job of explaining what needs to be true of the Allocator!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're right, I haven't explained here too much about the Allocator.
Allocator can be anything, as long as it has a constructor which receives ubyte[] and its ParentAllocator is the NullAllocator.
For the moment, these are the only constraints which come to mind.
I'll update the docs
Thanks!
|
It isn't clear to me that the |
|
@n8sh Besides the unittests, I stress tested this and I haven't found any races. |
478ecd5 to
1704139
Compare
| else alias parent = ParentAllocator.instance; | ||
|
|
||
| enum ulong alignment = theAlignment; | ||
| enum ulong alignment = ParentAllocator.alignment; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If alignment means the alignment of the memory returned by allocate, it would appear this should be Allocator.alignment instead of ParentAllocator.alignment.
Also maybe it should have a guard like:
static if (hasStaticallyKnownAlignment!Allocator)
enum alignment = Allocator.alignment;The existence of std.experimental.allocator.common : hasStaticallyKnownAlignment implies that not all allocators have statically known alignments, but this may be obsolete or not applicable to the specific allocators required here.
1704139 to
a16b23b
Compare
andralex
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please mind @wilzbach 's and my nits
| import std.experimental.allocator.building_blocks.null_allocator; | ||
|
|
||
| // Common function implementation for thread local and shared AlignedBlockList | ||
| private mixin template AlignedBlockListImpl(bool isShared) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bool is OK for a private artifact
| AlignedBlockNode* root; | ||
|
|
||
| // Number of active nodes | ||
| int numNodes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't size_t be better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
size_t is size of objects in memory. For counts we can use that of course, but just incidentally.
| int numNodes; | ||
|
|
||
| // If the numNodes exceeds this limit, we will start deallocating nodes | ||
| enum maxNodes = 64; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enum uint
|
|
||
| enum ulong alignment = Allocator.alignment; | ||
|
|
||
| static if (hasMember!(ParentAllocator, "owns")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
document
a16b23b to
0fb44b0
Compare
Implemented AlignedBlockList, an allocator which holds a list of other allocators and allows for constant time deallocations, using aligned allocations.
Internally, this holds a doubly linked list of other allocators. Each node contains an allocator, followed by its payload. Each node will be allocated by the parent allocator at addresses multiple of 'theAlignment'.
Whenever a deallocation happens, the buffer candidate for deallocation will be rounded to the nearest multiple of 'theAlignment', in order to find in constant time the allocator from which the buffer originated.
This will play a very important part in creating a fast and safe allocator.
Thanks,
Alex