Title: [268560] trunk/Source/WebCore
Revision
268560
Author
[email protected]
Date
2020-10-15 16:41:51 -0700 (Thu, 15 Oct 2020)

Log Message

Cache JS arguments in AudioWorkletProcessor::process() for performance
https://bugs.webkit.org/show_bug.cgi?id=217685

Reviewed by Geoffrey Garen.

Cache JS arguments in AudioWorkletProcessor::process() for performance.
Blink has the same optimization here:
- https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc

No new tests, no Web-facing behavior change.

* Modules/webaudio/AudioWorkletProcessor.cpp:
(WebCore::busChannelCount):
(WebCore::constructFrozenJSArray):
(WebCore::copyDataFromJSArrayToBuses):
(WebCore::copyDataFromBusesToJSArray):
(WebCore::copyDataFromParameterMapToJSObject):
(WebCore::busTopologyMatchesJSArray):
(WebCore::parameterMapTopologyMatchesJSObject):
(WebCore::zeroJSArray):
(WebCore::AudioWorkletProcessor::buildJSArguments):
(WebCore::AudioWorkletProcessor::process):
* Modules/webaudio/AudioWorkletProcessor.h:

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (268559 => 268560)


--- trunk/Source/WebCore/ChangeLog	2020-10-15 22:27:21 UTC (rev 268559)
+++ trunk/Source/WebCore/ChangeLog	2020-10-15 23:41:51 UTC (rev 268560)
@@ -1,5 +1,31 @@
 2020-10-15  Chris Dumez  <[email protected]>
 
+        Cache JS arguments in AudioWorkletProcessor::process() for performance
+        https://bugs.webkit.org/show_bug.cgi?id=217685
+
+        Reviewed by Geoffrey Garen.
+
+        Cache JS arguments in AudioWorkletProcessor::process() for performance.
+        Blink has the same optimization here:
+        - https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc
+
+        No new tests, no Web-facing behavior change.
+
+        * Modules/webaudio/AudioWorkletProcessor.cpp:
+        (WebCore::busChannelCount):
+        (WebCore::constructFrozenJSArray):
+        (WebCore::copyDataFromJSArrayToBuses):
+        (WebCore::copyDataFromBusesToJSArray):
+        (WebCore::copyDataFromParameterMapToJSObject):
+        (WebCore::busTopologyMatchesJSArray):
+        (WebCore::parameterMapTopologyMatchesJSObject):
+        (WebCore::zeroJSArray):
+        (WebCore::AudioWorkletProcessor::buildJSArguments):
+        (WebCore::AudioWorkletProcessor::process):
+        * Modules/webaudio/AudioWorkletProcessor.h:
+
+2020-10-15  Chris Dumez  <[email protected]>
+
         [Cocoa] Simplify logic for caching FFTSetups in FFTFrame
         https://bugs.webkit.org/show_bug.cgi?id=217782
 

Modified: trunk/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.cpp (268559 => 268560)


--- trunk/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.cpp	2020-10-15 22:27:21 UTC (rev 268559)
+++ trunk/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.cpp	2020-10-15 23:41:51 UTC (rev 268560)
@@ -45,6 +45,35 @@
 
 using namespace JSC;
 
+static unsigned busChannelCount(const AudioBus& bus)
+{
+    return bus.numberOfChannels();
+}
+
+static unsigned busChannelCount(const AudioBus* bus)
+{
+    return bus ? busChannelCount(*bus) : 0;
+}
+
+static JSArray* toJSArray(JSValueInWrappedObject& wrapper)
+{
+    return wrapper ? jsCast<JSArray*>(static_cast<JSValue>(wrapper)) : nullptr;
+}
+
+static JSObject* toJSObject(JSValueInWrappedObject& wrapper)
+{
+    return wrapper ? jsCast<JSObject*>(static_cast<JSValue>(wrapper)) : nullptr;
+}
+
+static void forEachChannelDataJSArray(JSGlobalObject& globalObject, JSArray& jsArray, const Function<void(unsigned busIndex, unsigned channelIndex, JSFloat32Array& channelData)>& apply)
+{
+    for (unsigned busIndex = 0, busCount = jsArray.length(); busIndex < busCount; ++busIndex) {
+        auto* channelsArray = jsCast<JSArray*>(jsArray.getIndex(&globalObject, busIndex));
+        for (unsigned channelIndex = 0, channelCount = channelsArray->length(); channelIndex < channelCount; ++channelIndex)
+            apply(busIndex, channelIndex, *jsCast<JSFloat32Array*>(channelsArray->getIndex(&globalObject, channelIndex)));
+    }
+}
+
 static JSFloat32Array* constructJSFloat32Array(JSGlobalObject& globalObject, unsigned length, const float* data = ""
 {
     auto* jsArray = JSFloat32Array::create(&globalObject, globalObject.typedArrayStructure(TypeFloat32), length);
@@ -70,9 +99,11 @@
 }
 
 enum class ShouldPopulateWithBusData : bool { No, Yes };
-static JSArray* constructFrozenJSArray(VM& vm, JSGlobalObject& globalObject, JSC::ThrowScope& scope, AudioBus* bus, ShouldPopulateWithBusData shouldPopulateWithBusData)
+
+template <typename T>
+static JSArray* constructFrozenJSArray(VM& vm, JSGlobalObject& globalObject, JSC::ThrowScope& scope, const T& bus, ShouldPopulateWithBusData shouldPopulateWithBusData)
 {
-    unsigned numberOfChannels = bus ? bus->numberOfChannels() : 0;
+    unsigned numberOfChannels = busChannelCount(bus.get());
     auto* channelsData = JSArray::create(vm, globalObject.originalArrayStructureForIndexingType(ArrayWithContiguous), numberOfChannels);
     for (unsigned j = 0; j < numberOfChannels; ++j) {
         auto* channel = bus->channel(j);
@@ -89,13 +120,13 @@
     auto scope = DECLARE_THROW_SCOPE(vm);
     auto* array = JSArray::create(vm, globalObject.originalArrayStructureForIndexingType(ArrayWithContiguous), buses.size());
     for (unsigned i = 0; i < buses.size(); ++i)
-        array->setIndexQuickly(vm, i, constructFrozenJSArray(vm, globalObject, scope, WTF::getPtr(buses[i]), shouldPopulateWithBusData));
+        array->setIndexQuickly(vm, i, constructFrozenJSArray(vm, globalObject, scope, buses[i], shouldPopulateWithBusData));
     JSC::objectConstructorFreeze(&globalObject, array);
     EXCEPTION_ASSERT(!scope.exception());
     return array;
 }
 
-static void copyDataFromJSArrayToBuses(JSGlobalObject& globalObject, JSArray& jsArray, Vector<Ref<AudioBus>>& buses)
+static void copyDataFromJSArrayToBuses(JSGlobalObject& globalObject, const JSArray& jsArray, Vector<Ref<AudioBus>>& buses)
 {
     // We can safely make assumptions about the structure of the JSArray since we use frozen arrays.
     for (unsigned i = 0; i < buses.size(); ++i) {
@@ -112,6 +143,72 @@
     }
 }
 
+static void copyDataFromBusesToJSArray(JSGlobalObject& globalObject, const Vector<RefPtr<AudioBus>>& buses, JSArray& jsArray)
+{
+    forEachChannelDataJSArray(globalObject, jsArray, [&](unsigned busIndex, unsigned channelIndex, JSFloat32Array& channelData) {
+        auto* channel = buses[busIndex]->channel(channelIndex);
+        ASSERT(channelData.length() == channel->length());
+        memcpy(channelData.typedVector(), channel->mutableData(), sizeof(float) * channel->length());
+    });
+}
+
+static void copyDataFromParameterMapToJSObject(VM& vm, JSGlobalObject& globalObject, const HashMap<String, std::unique_ptr<AudioFloatArray>>& paramValuesMap, JSObject& jsObject)
+{
+    for (auto& pair : paramValuesMap) {
+        auto* jsTypedArray = jsCast<JSFloat32Array*>(jsObject.get(&globalObject, Identifier::fromString(vm, pair.key)));
+        ASSERT(pair.value->size() >= jsTypedArray->length());
+        memcpy(jsTypedArray->typedVector(), pair.value->data(), sizeof(float) * jsTypedArray->length());
+    }
+}
+
+template<typename T>
+static bool busTopologyMatchesJSArray(JSGlobalObject& globalObject, const Vector<T>& buses, JSArray* jsArray)
+{
+    if (!jsArray)
+        return false;
+
+    ASSERT_WITH_MESSAGE(jsArray->length() == buses.size(), "Number of inputs/outputs cannot change after construction");
+
+    for (unsigned i = 0; i < buses.size(); ++i) {
+        auto& bus = buses[i];
+        auto* channelsArray = jsCast<JSArray*>(jsArray->getIndex(&globalObject, i));
+        unsigned numberOfChannels = busChannelCount(bus.get());
+        if (channelsArray->length() != numberOfChannels)
+            return false;
+
+        for (unsigned j = 0; j < numberOfChannels; ++j) {
+            auto* channel = bus->channel(j);
+            auto* jsChannelData = jsCast<JSFloat32Array*>(channelsArray->getIndex(&globalObject, j));
+            if (jsChannelData->length() != channel->length())
+                return false;
+        }
+    }
+
+    return true;
+}
+
+static bool parameterMapTopologyMatchesJSObject(VM& vm, JSGlobalObject& globalObject, const HashMap<String, std::unique_ptr<AudioFloatArray>>& paramValuesMap, JSObject* jsObject)
+{
+    if (!jsObject)
+        return false;
+
+    for (auto& pair : paramValuesMap) {
+        auto* jsTypedArray = jsCast<JSFloat32Array*>(jsObject->get(&globalObject, Identifier::fromString(vm, pair.key)));
+        unsigned expectedLength = pair.value->containsConstantValue() ? 1 : pair.value->size();
+        if (jsTypedArray->length() != expectedLength)
+            return false;
+    }
+
+    return true;
+}
+
+static void zeroJSArray(JSGlobalObject& globalObject, JSArray& jsArray)
+{
+    forEachChannelDataJSArray(globalObject, jsArray, [](unsigned, unsigned, JSFloat32Array& channelData) {
+        memset(channelData.typedVector(), 0, sizeof(float) * channelData.length());
+    });
+}
+
 ExceptionOr<Ref<AudioWorkletProcessor>> AudioWorkletProcessor::create(ScriptExecutionContext& context)
 {
     auto constructionData = downcast<AudioWorkletGlobalScope>(context).takePendingProcessorConstructionData();
@@ -130,6 +227,28 @@
     ASSERT(!isMainThread());
 }
 
+void AudioWorkletProcessor::buildJSArguments(VM& vm, JSGlobalObject& globalObject, MarkedArgumentBuffer& args, const Vector<RefPtr<AudioBus>>& inputs, Vector<Ref<AudioBus>>& outputs, const HashMap<String, std::unique_ptr<AudioFloatArray>>& paramValuesMap)
+{
+    // For performance reasons, we cache the arrays passed to JS and reconstruct them only when the topology changes.
+    if (busTopologyMatchesJSArray(globalObject, inputs, toJSArray(m_jsInputs)))
+        copyDataFromBusesToJSArray(globalObject, inputs, *toJSArray(m_jsInputs));
+    else
+        m_jsInputs = { constructFrozenJSArray(vm, globalObject, inputs, ShouldPopulateWithBusData::Yes) };
+    args.append(m_jsInputs);
+
+    if (busTopologyMatchesJSArray(globalObject, outputs, toJSArray(m_jsOutputs)))
+        zeroJSArray(globalObject, *toJSArray(m_jsOutputs));
+    else
+        m_jsOutputs = { constructFrozenJSArray(vm, globalObject, outputs, ShouldPopulateWithBusData::No) };
+    args.append(m_jsOutputs);
+
+    if (parameterMapTopologyMatchesJSObject(vm, globalObject, paramValuesMap, toJSObject(m_jsParamValues)))
+        copyDataFromParameterMapToJSObject(vm, globalObject, paramValuesMap, *toJSObject(m_jsParamValues));
+    else
+        m_jsParamValues = { constructFrozenKeyValueObject(vm, globalObject, paramValuesMap) };
+    args.append(m_jsParamValues);
+}
+
 bool AudioWorkletProcessor::process(const Vector<RefPtr<AudioBus>>& inputs, Vector<Ref<AudioBus>>& outputs, const HashMap<String, std::unique_ptr<AudioFloatArray>>& paramValuesMap, bool& threwException)
 {
     ASSERT(m_processCallback);
@@ -141,12 +260,7 @@
     JSLockHolder lock(vm);
 
     MarkedArgumentBuffer args;
-    // FIXME: We should consider caching the JSArrays & JSObject and only update their items
-    // every time process() is called, for performance reasons.
-    args.append(constructFrozenJSArray(vm, globalObject, inputs, ShouldPopulateWithBusData::Yes));
-    auto* ouputJSArray = constructFrozenJSArray(vm, globalObject, outputs, ShouldPopulateWithBusData::No);
-    args.append(ouputJSArray);
-    args.append(constructFrozenKeyValueObject(vm, globalObject, paramValuesMap));
+    buildJSArguments(vm, globalObject, args, inputs, outputs, paramValuesMap);
 
     NakedPtr<JSC::Exception> returnedException;
     auto result = m_processCallback->invokeCallback(jsUndefined(), args, JSCallbackData::CallbackType::Object, Identifier::fromString(vm, "process"), returnedException);
@@ -156,7 +270,7 @@
         return false;
     }
 
-    copyDataFromJSArrayToBuses(globalObject, *ouputJSArray, outputs);
+    copyDataFromJSArrayToBuses(globalObject, *toJSArray(m_jsOutputs), outputs);
 
     return result.toBoolean(&globalObject);
 }

Modified: trunk/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.h (268559 => 268560)


--- trunk/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.h	2020-10-15 22:27:21 UTC (rev 268559)
+++ trunk/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.h	2020-10-15 23:41:51 UTC (rev 268560)
@@ -31,10 +31,16 @@
 #if ENABLE(WEB_AUDIO)
 #include "AudioArray.h"
 #include "ExceptionOr.h"
+#include "JSValueInWrappedObject.h"
 #include <wtf/Forward.h>
 #include <wtf/Ref.h>
 #include <wtf/ThreadSafeRefCounted.h>
 
+namespace JSC {
+class JSArray;
+class MarkedArgumentBuffer;
+}
+
 namespace WebCore {
 
 class AudioBus;
@@ -55,12 +61,20 @@
 
     void setProcessCallback(std::unique_ptr<JSCallbackDataStrong>&&);
 
+    JSValueInWrappedObject& jsInputsWrapper() { return m_jsInputs; }
+    JSValueInWrappedObject& jsOutputsWrapper() { return m_jsOutputs; }
+    JSValueInWrappedObject& jsParamValuesWrapper() { return m_jsParamValues; }
+
 private:
     explicit AudioWorkletProcessor(const AudioWorkletProcessorConstructionData&);
+    void buildJSArguments(JSC::VM&, JSC::JSGlobalObject&, JSC::MarkedArgumentBuffer&, const Vector<RefPtr<AudioBus>>& inputs, Vector<Ref<AudioBus>>& outputs, const HashMap<String, std::unique_ptr<AudioFloatArray>>& paramValuesMap);
 
     String m_name;
     Ref<MessagePort> m_port;
     std::unique_ptr<JSCallbackDataStrong> m_processCallback;
+    JSValueInWrappedObject m_jsInputs;
+    JSValueInWrappedObject m_jsOutputs;
+    JSValueInWrappedObject m_jsParamValues;
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.idl (268559 => 268560)


--- trunk/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.idl	2020-10-15 22:27:21 UTC (rev 268559)
+++ trunk/Source/WebCore/Modules/webaudio/AudioWorkletProcessor.idl	2020-10-15 23:41:51 UTC (rev 268560)
@@ -29,7 +29,8 @@
 [
     Conditional=WEB_AUDIO,
     Exposed=AudioWorklet,
-    ImplementationLacksVTable
+    ImplementationLacksVTable,
+    JSCustomMarkFunction
 ] interface AudioWorkletProcessor {
     [CallWith=ScriptExecutionContext, MayThrowException] constructor();
     readonly attribute MessagePort port;

Modified: trunk/Source/WebCore/Sources.txt (268559 => 268560)


--- trunk/Source/WebCore/Sources.txt	2020-10-15 22:27:21 UTC (rev 268559)
+++ trunk/Source/WebCore/Sources.txt	2020-10-15 23:41:51 UTC (rev 268560)
@@ -472,6 +472,7 @@
 bindings/js/JSAttrCustom.cpp
 bindings/js/JSAudioTrackCustom.cpp
 bindings/js/JSAudioTrackListCustom.cpp
+bindings/js/JSAudioWorkletProcessorCustom.cpp
 bindings/js/JSAuthenticatorResponseCustom.cpp
 bindings/js/JSBasicCredentialCustom.cpp
 bindings/js/JSBlobCustom.cpp

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (268559 => 268560)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2020-10-15 22:27:21 UTC (rev 268559)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2020-10-15 23:41:51 UTC (rev 268560)
@@ -10837,6 +10837,7 @@
 		83EE598C1F50958B003E8B30 /* JSErrorCallback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSErrorCallback.cpp; sourceTree = "<group>"; };
 		83F28BFD24DB1DD3005BA6F6 /* ConvolverOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConvolverOptions.h; sourceTree = "<group>"; };
 		83F28BFF24DB1DD4005BA6F6 /* ConvolverOptions.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = ConvolverOptions.idl; sourceTree = "<group>"; };
+		83F37A672536B21B00FF5F3B /* JSAudioWorkletProcessorCustom.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSAudioWorkletProcessorCustom.cpp; sourceTree = "<group>"; };
 		83F570AD1C53268E007FD6CB /* JSXMLDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSXMLDocument.h; sourceTree = "<group>"; };
 		83F570AE1C53268E007FD6CB /* JSXMLDocument.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSXMLDocument.cpp; sourceTree = "<group>"; };
 		83F572941FA1066F003837BE /* JSServiceWorkerClientCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSServiceWorkerClientCustom.cpp; sourceTree = "<group>"; };
@@ -22229,6 +22230,7 @@
 				410626A72280A22A006D1B59 /* JSAudioNodeCustom.cpp */,
 				BE6DF70E171CA2DA00DD52B8 /* JSAudioTrackCustom.cpp */,
 				BE6DF710171CA2DA00DD52B8 /* JSAudioTrackListCustom.cpp */,
+				83F37A672536B21B00FF5F3B /* JSAudioWorkletProcessorCustom.cpp */,
 				576082562011BE0200116678 /* JSAuthenticatorResponseCustom.cpp */,
 				5760824F20118D8D00116678 /* JSBasicCredentialCustom.cpp */,
 				8931DE5A14C44C44000DC9D2 /* JSBlobCustom.cpp */,
@@ -30658,7 +30660,6 @@
 				51741D0F0B07259A00ED442C /* BackForwardClient.h in Headers */,
 				BCA8C81E11E3D36900812FB7 /* BackForwardController.h in Headers */,
 				51A1B87D2087C4C000979A75 /* BackForwardItemIdentifier.h in Headers */,
-				0F94721725323C2100F153C8 /* BackgroundImageGeometry.h in Headers */,
 				BC124EE80C2641CD009E2349 /* BarProp.h in Headers */,
 				460BB6161D0A1BF000221812 /* Base64Utilities.h in Headers */,
 				83198FBF24A160DD00420B05 /* BaseAudioContext.h in Headers */,

Added: trunk/Source/WebCore/bindings/js/JSAudioWorkletProcessorCustom.cpp (0 => 268560)


--- trunk/Source/WebCore/bindings/js/JSAudioWorkletProcessorCustom.cpp	                        (rev 0)
+++ trunk/Source/WebCore/bindings/js/JSAudioWorkletProcessorCustom.cpp	2020-10-15 23:41:51 UTC (rev 268560)
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "JSAudioWorkletProcessor.h"
+
+#if ENABLE(WEB_AUDIO)
+
+namespace WebCore {
+using namespace JSC;
+
+void JSAudioWorkletProcessor::visitAdditionalChildren(SlotVisitor& visitor)
+{
+    auto& processor = wrapped();
+    processor.jsInputsWrapper().visit(visitor);
+    processor.jsOutputsWrapper().visit(visitor);
+    processor.jsParamValuesWrapper().visit(visitor);
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_AUDIO)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to