Uh oh!
There was an error while loading. Please reload this page.
buffer: improve creation performance - #6893
Conversation
RReverser
commented
May 20, 2016
cc @trevnorris as per request |
targos
commented
May 20, 2016
RReverser
commented
May 20, 2016
CI finished, I see only two failures in Windows build configs which are network timeouts. I guess unrelated? |
Fishrock123
commented
May 20, 2016
Hasn't Trevor already changed this between JS and C++ like 3 times? 😂 |
@Fishrock123 Well, it's mostly in JS, but the inheritance from TL;DR: the biggest win here is unrelated to the C++ change, but rather to the ES6 native subclassing. |
jasnell
commented
May 20, 2016
@nodejs/buffer |
eljefedelrodeodeljefe
commented
May 21, 2016
I like the changes very much. Also makes it more readable... I'll test around |
RReverser
commented
May 22, 2016
Well, now Buffer.slice is very close to Uint8Array.subarray (I was comparing against it in my local benchmarks as a "theoretical maximum" which obviously can't be achieved in a wrapper, but can be very close (and it is now)). Please do let me know if I missed something / need to change before this can be merged. |
There was a problem hiding this comment.
Side note: this comment is irrelevant now, probably since dd67608, I overlooked it.
/cc @trevnorris
There was a problem hiding this comment.
Looks like it can be removed.
There was a problem hiding this comment.
Looks like it has returned after a rebase =).
It's not critical, though, that could be removed later.
RReverser
commented
May 22, 2016
Not directly related, but now that I'm trying to submit changes from my Windows machine (previous one was from Mac), I've found one error: What would be the best fix for this - a PR that removes |
addaleax
commented
May 22, 2016
I think that discussion would best be taken to #6912 :) |
RReverser
commented
May 22, 2016
Oh cool, thanks! That's a new issue I haven't noticed yet :) |
RReverser
commented
May 22, 2016
Btw, can someone please explain what is for / supposed to do? Just tried to wrap my head around it, and wasn't sure whether the additional semantics on top of simple |
addaleax
commented
May 22, 2016
There was some discussion on that in #2635… though I can’t seem to find the advantage of using |
addaleax
commented
May 22, 2016
Note that it only exists as part of a deprecated API anyway. |
RReverser
commented
May 22, 2016
Exactly my thoughts.
That's true, just looks pretty weird when trying to read / understand the code. |
There was a problem hiding this comment.
This should preferably use Buffer.from instead of new Buffer
addaleax
commented
May 22, 2016
I’d maybe separate the LGTM either way. |
RReverser
commented
May 22, 2016
addaleax
commented
May 22, 2016
@RReverser Kinda… you don’t have to move that if you don’t want to, but keep in mind that the commit history ideally still makes sense for someone looking at it in a few years, without having the context of this PR in mind. Happens more often than you think. :) Also, it would be cool if you could re-format the commit message for that commit so that it adheres to the guidelines (i.e. it starts with |
trevnorris
commented
Jun 3, 2016
Hm. If this PR addresses the comment removal, leave it in its own commit. Yes it's minor, but if this needs to be reverted for some unforeseen reason don't want the comment coming back in. As far as the regression, I vote we leave that to its own PR (since it'll require it's own regression test, etc.). |
addaleax
commented
Jun 3, 2016
@trevnorris Want to go ahead and land this then? |
| if (size <= 0) | ||
| return createBuffer(size); | ||
| if (fill !== undefined) { | ||
| if (size > 0 && fill !== undefined) { |
There was a problem hiding this comment.
Does a separate check for 0 make sense here, i.e. size > 0 && fill !== undefined && fill !== 0?
Buffer.alloc(size, 0) is equivalent to Buffer.alloc(size), so just new FastBuffer(size) should work faster in that case.
There was a problem hiding this comment.
Hm, I think that would require benchmarking – createUnsafeBuffer() returns a slice from the pool, so I’d actually expect that to be faster than an extra typed array allocation.
There was a problem hiding this comment.
@addaleax It's not just createUnsafeBuffer, it's createUnsafeBuffer + fill(0).
I believe that has been discussed before, and allocation was proven to be faster — that's why simple Buffer.alloc(size) does not use the pool.
There was a problem hiding this comment.
@ChALkeR I know there’s the extra fill() in there. But if you say it’s faster, I believe that :)
There was a problem hiding this comment.
@addaleax Btw, nothing in this function uses slices from the pool. Perhaps it should?
Both new FastBuffer and createUnsafeBuffer just directly allocate a new instance.
There was a problem hiding this comment.
Oh, right. Maybe, but I’d leave that open for another PR, too, especially as it would introduce the subtle change that the return values of Buffer.alloc() would share their buffer property.
There was a problem hiding this comment.
i'm down for that change. The kernel can probably optimize calls to calloc() a hair better. Though not going to consider it a blocking change.
ChALkeR
commented
Jun 3, 2016
LGTM |
trevnorris
commented
Jun 3, 2016
@addaleax Going ahead w/ these sounds good to me. |
ChALkeR
commented
Jun 3, 2016
Curious: would Buffer.alloc=function(size,fill,encoding){assertSize(size);if(size>0&&fill!==undefined&&fill!==0){if(typeofencoding!=='string')encoding=undefined;returnallocate(size).fill(fill,encoding);}returnnewFastBuffer(size);};be faster for short buffers filled with some non-zero argument? There are two changes here: |
On a second thought, we can do that in a separate PR, those are independent changes. |
addaleax
commented
Jun 6, 2016
I’m going to land this later today if nobody beats me to it. |
RReverser
commented
Jun 6, 2016
Yes, thought about similar further optimizations, but they would be rather backward-incompatible and cases where they give any win are more rare, so decided not to change. |
Improves performance of allocating unsafe buffers, creating buffers from an existing ArrayBuffer and creating .slice(...) from existing Buffer by avoiding deoptimizing change of prototype after Uint8Array allocation in favor of ES6 native subclassing. This is done through an internal ES6 class that extends Uint8Array and is used for allocations, but the regular Buffer function is exposed, so calling Buffer(...) with or without `new` continues to work as usual and prototype chains are also preserved. Performance wins for .slice are +120% (2.2x), and, consequently, for unsafe allocations up to +95% (1.9x) for small buffers, and for safe allocations (zero-filled) up to +30% (1.3x). PR-URL: #6893 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
addaleax
commented
Jun 6, 2016
Landed in 5292a13. Thanks for the contribution and for your patience with us! |
RReverser
commented
Jun 6, 2016
@addaleax Thank you! That was quite a trip, but totally fine as for the first PR to the project :) |
addaleax
commented
Jun 6, 2016
No arguing about that. 😄 If you like, you can also do PRs for some of the issues that popped up as side notes in the discussion here. If not, you don’t have to, of course.
… if I’ve managed to get everything right. ;) |
ChALkeR
commented
Jun 6, 2016
@addaleax Also |
RReverser
commented
Jun 6, 2016
addaleax
commented
Jun 6, 2016
@RReverser sounds good, yup :) |
trevnorris
commented
Jun 7, 2016
@ChALkeR It was deliberate to not have varb;while((b=Buffer.allocUnsafe(1)).byteOffset>0);Buffer.from(b.buffer).fill(0);setTimeout(()=>{// See what else has been written to the buffer sinceconsole.log(b);},3000);Can collect more information by messing with |
@trevnorris Ah, understood. I personally don't see how that is a problem, because But ok, let's keep it that way if there are concerns about that. Perhaps that should be documented as a small one-line comment in the source code? |
trevnorris
commented
Jun 7, 2016
@ChALkeR That decision was simply my call when it was first implemented to make sure the PR would avoid additional scrutiny. If everyone's alright with using the pool then I won't stand in the way. |
ChALkeR
commented
Jun 8, 2016
@trevnorris On a second though, I think that you are correct here and that we should keep that as it is now. There could be various code errors on user side which could potentially cause issues if the code somehow uses the Also, the current behaviour is documented, and changing that would be a semver-major. So let's not change that =). |
evanlucas
commented
Jun 16, 2016
This depends on #7082 and #7093, both of which have been marked dont-land-on-v6.x. @RReverser interested in opening a backport PR against the v6.x branch? |
RReverser
commented
Jun 16, 2016
#7176 (comment) same question here |
Improves performance of allocating unsafe buffers, creating buffers from an existing ArrayBuffer and creating .slice(...) from existing Buffer by avoiding deoptimizing change of prototype after Uint8Array allocation in favor of ES6 native subclassing. This is done through an internal ES6 class that extends Uint8Array and is used for allocations, but the regular Buffer function is exposed, so calling Buffer(...) with or without `new` continues to work as usual and prototype chains are also preserved. Performance wins for .slice are +120% (2.2x), and, consequently, for unsafe allocations up to +95% (1.9x) for small buffers, and for safe allocations (zero-filled) up to +30% (1.3x). PR-URL: #7349 Ref: #6893 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com> Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Checklist
Affected core subsystem(s)
buffer
Description of change
Improves performance of allocating unsafe buffers, creating buffers from
an existing ArrayBuffer and creating .slice(...) from existing Buffer by
avoiding deoptimizing change of prototype after Uint8Array allocation
in favor of ES6 native subclassing.
This is done through an internal ES6 class that extends Uint8Array and
is used for allocations, but the regular Buffer function is exposed, so
calling Buffer(...) with or without
newcontinues to work as usualand prototype chains are also preserved.
Performance wins for .slice are +120% (2.2x), and, consequently, for
unsafe allocations up to +95% (1.9x) for small buffers, and for safe
allocations (zero-filled) up to +30% (1.3x).