Bug 170579 - Add a PriorityQueue class
Summary: Add a PriorityQueue class
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: New Bugs (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Keith Miller
URL:
Keywords:
Depends on:
Blocks: 170614
  Show dependency treegraph
 
Reported: 2017-04-06 17:41 PDT by Keith Miller
Modified: 2017-04-11 10:50 PDT (History)
15 users (show)

See Also:


Attachments
Patch (35.79 KB, patch)
2017-04-06 17:54 PDT, Keith Miller
no flags Details | Formatted Diff | Diff
Patch (35.82 KB, patch)
2017-04-06 18:26 PDT, Keith Miller
no flags Details | Formatted Diff | Diff
Patch (37.44 KB, patch)
2017-04-07 12:56 PDT, Keith Miller
no flags Details | Formatted Diff | Diff
Patch (39.52 KB, patch)
2017-04-07 14:40 PDT, Keith Miller
no flags Details | Formatted Diff | Diff
Patch for landing (39.51 KB, patch)
2017-04-07 14:40 PDT, Keith Miller
no flags Details | Formatted Diff | Diff
Patch for landing (40.03 KB, patch)
2017-04-07 15:01 PDT, Keith Miller
no flags Details | Formatted Diff | Diff
Patch for landing (39.99 KB, patch)
2017-04-07 17:35 PDT, Keith Miller
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Keith Miller 2017-04-06 17:41:03 PDT
Add PriorityQueue class
Comment 1 Keith Miller 2017-04-06 17:54:29 PDT
Created attachment 306450 [details]
Patch
Comment 2 Yusuke Suzuki 2017-04-06 18:16:27 PDT
Comment on attachment 306450 [details]
Patch

I think we can use std::push_heap & std::pop_heap to simplify the implementation. What do you think of?
Comment 3 Keith Miller 2017-04-06 18:26:34 PDT
Created attachment 306453 [details]
Patch
Comment 4 Keith Miller 2017-04-06 18:46:37 PDT
(In reply to Yusuke Suzuki from comment #2)
> Comment on attachment 306450 [details]
> Patch
> 
> I think we can use std::push_heap & std::pop_heap to simplify the
> implementation. What do you think of?

I'm not sure that really helps since we need to have a siftUp and siftDown for the increaseKey/decreaseKey methods.
Comment 5 Yusuke Suzuki 2017-04-06 19:57:51 PDT
(In reply to Keith Miller from comment #4)
> (In reply to Yusuke Suzuki from comment #2)
> > Comment on attachment 306450 [details]
> > Patch
> > 
> > I think we can use std::push_heap & std::pop_heap to simplify the
> > implementation. What do you think of?
> 
> I'm not sure that really helps since we need to have a siftUp and siftDown
> for the increaseKey/decreaseKey methods.

Seems right. While increaseKey can be implemented in push_heap, decreaseKey requires siftDown which is not directly offered by stl.
Comment 6 Saam Barati 2017-04-07 00:39:01 PDT
Comment on attachment 306453 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=306453&action=review

PriorityQueue LGTM, just one thing looks weird to me about how you're using this in Plan. Comments below.

> Source/WTF/ChangeLog:19
> +        The only way to update the location of an entry is to do a hash
> +        table lookup with the entry's hash but this is probably more
> +        expensive than just doing a linear search.

This seems unlikely to be true if the heap gets large. Perhaps it's worth filing a bug to investigate this if it ever becomes an issue?

> Source/JavaScriptCore/wasm/WasmWorklist.cpp:112
> +            worklist.m_queue.enqueue(WTFMove(element));
>              worklist.m_planEnqueued->notifyAll(locker);
> +            element = worklist.m_queue.peek();

I'm confused as to what this is doing? What if you get back a different element when you do this?

Actually, this also looks like a potential problem in the old queue as well, except that it'd naturally handle it since it never WTFMove() the element, so in the worst case, you'd notify all your threads and they'd grab a bunch of new plans and validate them, instead of compiling the element's plan in parallel.

> Source/WTF/wtf/PriorityQueue.h:39
> +//

Style: remove empty "//"

> Source/WTF/wtf/PriorityQueue.h:54
> +    void enqueue(T&& element)
> +    {
> +        size_t location = m_buffer.size();
> +        m_buffer.append(WTFMove(element));
> +        siftUp(location);
> +    }

You should use forwarding semantics here.

> Source/WTF/wtf/PriorityQueue.h:106
> +    size_t parentOf(size_t location) const { ASSERT(location); return (location - 1) / 2; }
> +    size_t leftChildOf(size_t location) const { return location * 2 + 1; }
> +    size_t rightChildOf(size_t location) const { return leftChildOf(location) + 1; }

Style nit: I'd make these static constexpr

> Source/WTF/wtf/PriorityQueue.h:138
> +    IsHigherPriority isHigherPriority;

It's questionable that you need to instantiate a value here. Why not just have an API here that requires some static function called isHigherPriority(a, b)?

> Tools/TestWebKitAPI/Tests/WTF/PriorityQueue.cpp:34
> +constexpr std::size_t operator "" _z ( unsigned long long n ) { return n; }

Is this needed? You only use it in 2 places.

> Tools/TestWebKitAPI/Tests/WTF/PriorityQueue.cpp:196
> +}

It might also be worth having a test based off randomness, but for values being enqueued/dequeued, and total length of the test.
Comment 7 Keith Miller 2017-04-07 12:23:59 PDT
Comment on attachment 306453 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=306453&action=review

>> Source/WTF/ChangeLog:19
>> +        expensive than just doing a linear search.
> 
> This seems unlikely to be true if the heap gets large. Perhaps it's worth filing a bug to investigate this if it ever becomes an issue?

Sounds good. I didn't mention this but it's also pretty rare that we actually use the  increase/decreaseKey functions so the cost of a O(n) is pretty rare. Also, the hash cost applies to enqueue and dequeue as well so changing the value of the key would likely need to be very common to outweigh the cost of searching.

>> Source/JavaScriptCore/wasm/WasmWorklist.cpp:112
>> +            element = worklist.m_queue.peek();
> 
> I'm confused as to what this is doing? What if you get back a different element when you do this?
> 
> Actually, this also looks like a potential problem in the old queue as well, except that it'd naturally handle it since it never WTFMove() the element, so in the worst case, you'd notify all your threads and they'd grab a bunch of new plans and validate them, instead of compiling the element's plan in parallel.

I think this works but only because compilation is strictly higher priority than validation. It might be conceptually simpler to poll again instead, however. I'll make that change.

>> Source/WTF/wtf/PriorityQueue.h:39
>> +//
> 
> Style: remove empty "//"

Fixed.

>> Source/WTF/wtf/PriorityQueue.h:54
>> +    }
> 
> You should use forwarding semantics here.

Fixed.

>> Source/WTF/wtf/PriorityQueue.h:106
>> +    size_t rightChildOf(size_t location) const { return leftChildOf(location) + 1; }
> 
> Style nit: I'd make these static constexpr

Changed.

>> Source/WTF/wtf/PriorityQueue.h:138
>> +    IsHigherPriority isHigherPriority;
> 
> It's questionable that you need to instantiate a value here. Why not just have an API here that requires some static function called isHigherPriority(a, b)?

Otherwise, you can't use std::less and friends as the comparator. If you think it's worth it we could implement our own template isLess, isGreater, etc.

>> Tools/TestWebKitAPI/Tests/WTF/PriorityQueue.cpp:34
>> +constexpr std::size_t operator "" _z ( unsigned long long n ) { return n; }
> 
> Is this needed? You only use it in 2 places.

Either way. I can replace it with a const size_t zero.

>> Tools/TestWebKitAPI/Tests/WTF/PriorityQueue.cpp:196
>> +}
> 
> It might also be worth having a test based off randomness, but for values being enqueued/dequeued, and total length of the test.

Sounds good.
Comment 8 Keith Miller 2017-04-07 12:56:10 PDT
Created attachment 306530 [details]
Patch
Comment 9 Saam Barati 2017-04-07 13:02:21 PDT
Comment on attachment 306530 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=306530&action=review

r=me

> Source/JavaScriptCore/wasm/WasmWorklist.cpp:184
> +    while (!m_queue.isEmpty()) {
> +        QueueElement element = m_queue.dequeue();
>          if (&element.plan->vm() == &vm) {
>              element.plan->cancel();
> -            return IterateResult::DropAndContinue;
> +            continue;
>          }

You need to rebase. This is no longer the code.

> Source/WTF/wtf/PriorityQueue.h:39
> +//

Please remove this.

> Source/WTF/wtf/PriorityQueue.h:138
> +    IsHigherPriority isHigherPriority;

I still don't quite understand why we need this, instead of something akin to HashTraits. Maybe file a bug to do this? It's unfortunate to make this class use more memory than needed. The only use of this class ATM will be a struct with no field members. It could just as easily be some abstract API.
Comment 10 Keith Miller 2017-04-07 14:40:07 PDT
Created attachment 306536 [details]
Patch
Comment 11 Keith Miller 2017-04-07 14:40:35 PDT
Created attachment 306537 [details]
Patch for landing
Comment 12 WebKit Commit Bot 2017-04-07 14:42:48 PDT
Comment on attachment 306537 [details]
Patch for landing

Rejecting attachment 306537 [details] from commit-queue.

Failed to run "['/Volumes/Data/EWS/WebKit/Tools/Scripts/webkit-patch', '--status-host=webkit-queues.webkit.org', '--bot-id=webkit-cq-01', 'apply-attachment', '--no-update', '--non-interactive', 306537, '--port=mac']" exit_code: 2 cwd: /Volumes/Data/EWS/WebKit

Last 500 characters of output:
ScriptCore/wasm/WasmWorklist.h
patching file Source/WTF/WTF.xcodeproj/project.pbxproj
patching file Source/WTF/wtf/MathExtras.h
patching file Source/WTF/wtf/PriorityQueue.h
patching file Tools/ChangeLog
Hunk #1 succeeded at 1 with fuzz 3.
patching file Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
patching file Tools/TestWebKitAPI/Tests/WTF/PriorityQueue.cpp

Failed to run "[u'/Volumes/Data/EWS/WebKit/Tools/Scripts/svn-apply', '--force']" exit_code: 1 cwd: /Volumes/Data/EWS/WebKit

Full output: http://webkit-queues.webkit.org/results/3492593
Comment 13 Keith Miller 2017-04-07 15:01:57 PDT
Created attachment 306539 [details]
Patch for landing
Comment 14 WebKit Commit Bot 2017-04-07 15:30:30 PDT
Comment on attachment 306539 [details]
Patch for landing

Rejecting attachment 306539 [details] from commit-queue.

Failed to run "['/Volumes/Data/EWS/WebKit/Tools/Scripts/webkit-patch', '--status-host=webkit-queues.webkit.org', '--bot-id=webkit-cq-01', 'build', '--no-clean', '--no-update', '--build-style=release', '--port=mac']" exit_code: 2 cwd: /Volumes/Data/EWS/WebKit

Last 500 characters of output:
athExtras.dia -c /Volumes/Data/EWS/WebKit/Source/JavaScriptCore/b3/B3MathExtras.cpp -o /Volumes/Data/EWS/WebKit/WebKitBuild/JavaScriptCore.build/Release/JavaScriptCore.build/Objects-normal/x86_64/B3MathExtras.o

** BUILD FAILED **


The following build commands failed:
	CompileC /Volumes/Data/EWS/WebKit/WebKitBuild/JavaScriptCore.build/Release/JavaScriptCore.build/Objects-normal/x86_64/WasmWorklist.o wasm/WasmWorklist.cpp normal x86_64 c++ com.apple.compilers.llvm.clang.1_0.compiler
(1 failure)

Full output: http://webkit-queues.webkit.org/results/3492675
Comment 15 Keith Miller 2017-04-07 17:35:02 PDT
Created attachment 306554 [details]
Patch for landing
Comment 16 WebKit Commit Bot 2017-04-07 18:15:12 PDT
Comment on attachment 306554 [details]
Patch for landing

Clearing flags on attachment: 306554

Committed r215135: <http://trac.webkit.org/changeset/215135>
Comment 17 WebKit Commit Bot 2017-04-07 18:15:14 PDT
All reviewed patches have been landed.  Closing bug.
Comment 18 Csaba Osztrogonác 2017-04-08 01:34:53 PDT
(In reply to WebKit Commit Bot from comment #16)
> Comment on attachment 306554 [details]
> Patch for landing
> 
> Clearing flags on attachment: 306554
> 
> Committed r215135: <http://trac.webkit.org/changeset/215135>

It broke the GTK build:
In file included from ../../Source/JavaScriptCore/wasm/WasmWorklist.h:35:0,
                 from ../../Source/JavaScriptCore/wasm/WasmWorklist.cpp:26:
../../Source/WTF/wtf/PriorityQueue.h: In instantiation of ‘static constexpr size_t WTF::PriorityQueue<T, isHigherPriority, inlineCapacity>::parentOf(size_t) [with T = JSC::Wasm::Worklist::QueueElement; bool (* isHigherPriority)(const T&, const T&) = JSC::Wasm::Worklist::isHigherPriority; long unsigned int inlineCapacity = 10ul; size_t = long unsigned int]’:
../../Source/WTF/wtf/PriorityQueue.h:110:44:   required from ‘void WTF::PriorityQueue<T, isHigherPriority, inlineCapacity>::siftUp(size_t) [with T = JSC::Wasm::Worklist::QueueElement; bool (* isHigherPriority)(const T&, const T&) = JSC::Wasm::Worklist::isHigherPriority; long unsigned int inlineCapacity = 10ul; size_t = long unsigned int]’
../../Source/WTF/wtf/PriorityQueue.h:52:24:   required from ‘void WTF::PriorityQueue<T, isHigherPriority, inlineCapacity>::enqueue(T) [with T = JSC::Wasm::Worklist::QueueElement; bool (* isHigherPriority)(const T&, const T&) = JSC::Wasm::Worklist::isHigherPriority; long unsigned int inlineCapacity = 10ul]’
../../Source/JavaScriptCore/wasm/WasmWorklist.cpp:112:81:   required from here
../../Source/WTF/wtf/PriorityQueue.h:103:95: error: body of constexpr function ‘static constexpr size_t WTF::PriorityQueue<T, isHigherPriority, inlineCapacity>::parentOf(size_t) [with T = JSC::Wasm::Worklist::QueueElement; bool (* isHigherPriority)(const T&, const T&) = JSC::Wasm::Worklist::isHigherPriority; long unsigned int inlineCapacity = 10ul; size_t = long unsigned int]’ not a return-statement
     static constexpr size_t parentOf(size_t location) { ASSERT(location); return (location - 1) / 2; }
                                                                                               ^
Comment 19 Geoffrey Garen 2017-04-10 09:22:23 PDT
Comment on attachment 306554 [details]
Patch for landing

The std library has built-in heap functions. You should consider using those. (std has a priority queue as well. The only reason not to use it is that it will do the slow kind of allocation.)
Comment 20 Darin Adler 2017-04-11 09:14:22 PDT
When I needed a priority queue in the Timer.cpp implementation, I used std::push_heap and std::pop_heap. RunLoopGeneric.cpp and LegacyTileGrid.mm also use this approach.
Comment 21 Keith Miller 2017-04-11 09:47:02 PDT
(In reply to Geoffrey Garen from comment #19)
> The std library has built-in heap functions. You should consider using those.

(In reply to Darin Adler from comment #20)
> When I needed a priority queue in the Timer.cpp implementation, I used
> std::push_heap and std::pop_heap. RunLoopGeneric.cpp and LegacyTileGrid.mm
> also use this approach.

I considered using the std::push_heap and std::pop_heap methods but it's not possible to implement decreaseKey() with std::pop_heap and there is no officially supported std::sift_down method. Although, it is possible to implement increaseKey() with std::push_heap.

(In reply to Geoffrey Garen from comment #19)
> (std has a priority queue as well. The only reason not to use it is
> that it will do the slow kind of allocation.)

std::priority_queue does not support iterating the queue, which is important for the Wasm use case. Additionally, std::priority_queue does not support an increaseKey or decreaseKey method. While I could subclass std::priority_queue and implement those methods, if I'm going to go through that effort I might as well make a priority queue with all the bells and whistles I want.
Comment 22 Geoffrey Garen 2017-04-11 10:50:14 PDT
Since increaseKey and decreaseKey are O(n) in their implementations here, you could get similar performance using std by just changing a key and then calling make_heap again.

I guess you've saved a little bit of performance since, after your O(n) search, you move the entry in O(log(n)) time, whereas make_heap would move the entry in O(n) time.