惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
C
Cisco Blogs
Cloudbric
Cloudbric
The Last Watchdog
The Last Watchdog
L
LINUX DO - 热门话题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Security Archives - TechRepublic
Security Archives - TechRepublic
TaoSecurity Blog
TaoSecurity Blog
V2EX - 技术
V2EX - 技术
H
Heimdal Security Blog
S
Security Affairs
L
Lohrmann on Cybersecurity
Hacker News - Newest:
Hacker News - Newest: "LLM"
Simon Willison's Weblog
Simon Willison's Weblog
WordPress大学
WordPress大学
小众软件
小众软件
Security Latest
Security Latest
AWS News Blog
AWS News Blog
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
Engineering at Meta
Engineering at Meta
阮一峰的网络日志
阮一峰的网络日志
罗磊的独立博客
F
Full Disclosure
S
Schneier on Security
L
LangChain Blog
MyScale Blog
MyScale Blog
Know Your Adversary
Know Your Adversary
P
Privacy International News Feed
Google Online Security Blog
Google Online Security Blog
Scott Helme
Scott Helme
Stack Overflow Blog
Stack Overflow Blog
爱范儿
爱范儿
A
Arctic Wolf
Martin Fowler
Martin Fowler
B
Blog RSS Feed
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
The Register - Security
The Register - Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园_首页
Latest news
Latest news
F
Fortinet All Blogs
G
GRAHAM CLULEY
T
The Exploit Database - CXSecurity.com
Hacker News: Ask HN
Hacker News: Ask HN

webrtcHacks

Vibe-coding WebRTC - webrtcHacks WebRTC vs. MoQ by Use Case - webrtcHacks Is everyone switching to MoQ from WebRTC? - webrtcHacks How OpenAI does WebRTC in the new gpt-realtime - webrtcHacks How WebRTC’s NetEQ Jitter Buffer Provides Smooth Audio - webrtcHacks OpenAI & WebRTC Q&A with Sean DuBois - webrtcHacks Measuring the response latency of OpenAIs WebRTC-based Realtime API - webrtcHacks The Unofficial Guide to OpenAI’s (Beta) Realtime WebRTC API - webrtcHacks 2024 WebRTC in Open Source Review: A Quantitative Analysis - webrtcHacks
Vector Vibing to speed up Opus encode by 20% - webrtcHacks
Philipp Hancke · 2026-07-02 · via webrtcHacks

tl;dr: Opus encoding just got 20% faster on desktop Chromium by turning on AVX2 vectorization in libopus.

Vectorization means one instruction doing the same arithmetic on a whole vector of values at once instead of one at a time. Codecs spend their hot loops running identical math across long buffers of samples, exactly where vectorization (SIMD instruction sets like AVX2 on desktop or NEON on ARM) is highly effective.

In November 2018, the person wiring up Opus’s SSE4.1 kernels in Chromium left a note for the future:
if libopus ever adds AVX support, add an opus_avx block.

This was done in the BUILD.gn file which controls how libopus is built inside Chromium. In 2018 libopus had no AVX functions but it was well understood that when, not if, Opus adds AVX2 this is going to be a win!

Then libopus added said AVX2 support. Years later. The three AVX2 kernels rode into the Chromium source code with the 1.5.2 roll in April 2024 (that release even shipped a fix for an AVX2 alignment crash, so the code was not merely present but actively maintained in libopus), fully wired into Opus’s runtime CPU dispatch, sitting in the tree but not being built. The build still stopped at SSE4.1, behind a stale comment insisting libopus had no AVX functions.

The comment outlived its own premise. Addressing it led to a 23% reduction of Opus encode time on the desktop VoIP path. Now, how much 23% of the audio encode time matters is a different question but it is hard to argue that it is not a win.

Vectorizing codec hot paths is a no-brainer. The interesting questions were and are:

  • how much faster did it get
  • where does one need to optimize
  • how do you measure the results

Those are the questions that historically took a week to answer. Minions to the rescue, Claude Opus 4.8 this time!

The change itself is the boring part

Here is the whole patch:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

--- a/third_party/opus/BUILD.gn

+++ b/third_party/opus/BUILD.gn

@@ -75,9 +75,12 @@

       # Some systems may have SSE4.1+ support.

       "OPUS_X86_MAY_HAVE_SSE4_1",

-      # At present libopus has no AVX functions so no sources are add for this,

-      # if you see linker errors on AVX code the this flag is why.

-      "OPUS_X86_MAY_HAVE_AVX",

+      # Some systems may have AVX2 support. libopus provides AVX2 kernels

+      # (celt_pitch_xcorr, silk_NSQ_del_dec, silk_inner_product_FLP) that are

+      # selected at runtime via RTCD; the actual sources are built in the

+      # ":opus_avx2" source_set below. MAY_HAVE (not PRESUME) so non-AVX2 CPUs

+      # fall back to SSE4.1/C at runtime.

+      "OPUS_X86_MAY_HAVE_AVX2",

     ]

   }

@@ -158,7 +161,32 @@

       cflags = [ "-msse4.1" ]

     }

   }

-  # TODO(dalecurtis): If libopus ever adds AVX support, add an opus_avx block.

+

+  source_set("opus_avx2") {

+    sources = [

+      "src/celt/x86/pitch_avx.c",

+      "src/silk/float/x86/inner_product_FLP_avx2.c",

+      "src/silk/x86/NSQ_del_dec_avx2.c",

+    ]

+

+    configs -= [ "//build/config/compiler:chromium_code" ]

+    configs += [ "//build/config/compiler:no_chromium_code" ]

+    configs += [

+      ":opus_private_config",

+      ":opus_config",

+    ]

+

+    if (!is_win || is_clang) {

+      # Matches upstream DEFAULT_X86_AVX2_CFLAGS (configure.ac).

+      cflags = [

+        "-mavx",

+        "-mfma",

+        "-mavx2",

+      ]

+    } else {

+      cflags = [ "/arch:AVX2" ]

+    }

+  }

}

@@ -404,7 +432,10 @@

       "src/silk/x86/main_sse.h",

       "src/silk/x86/x86_silk_map.c",

     ]

-    deps += [ ":opus_sse41" ]

+    deps += [

+      ":opus_avx2",

+      ":opus_sse41",

+    ]

   }

It does change the BUILD.gn file to

  • replace the now no-op OPUS_X86_MAY_HAVE_AVX with OPUS_X86_MAY_HAVE_AVX2
  • add an opus_avx2 source_set that compiles the three _avx2.c kernels with -mavx -mfma -mavx2
  • pull it into the opus target’s deps

No edit to the upstream code, the kernels were already there. MAY_HAVE, not PRESUME, so runtime CPU detection stays in charge and older machines fall back to SSE4.1/C. Thirty-ish lines of build config, and one of them deletes a TODO. I did not spend my morning on those thirty lines, thankfully.

The part I used to not have a week for

I spent it on the part that comes after: proving the change is effective.

The existing benchmark in libWebRTC drives the real WebRtcOpus_Encode wrapper over the in-tree speech and music vectors, looped to 400 seconds per case. Pinned to one core, median of five, on my machine. The same BUILD.gn stash-toggled between runs so opus recompiles with and without AVX2 and everything else is bit-exact. That used to be the finicky, easy-to-botch part I would half-do. Doing it properly is now cheap, so it gets done properly. With fancy graphs instead of an Excel sheet even.

And then the actual analysis, which is where it gets fun (to watch).

Case (complexity 9) Baseline (SSE4.1) AVX2 Delta encode
mono speech, 64 kbps 1409 ms 1163 ms -17.5%
mono speech, 32 kbps 1733 ms 1336 ms -22.9%
stereo music, 64 kbps 1156 ms 1133 ms -2.1%
Encoder CPU reduction by case: AVX2 vs SSE4.1
Encoder CPU time reduction per case (complexity 9). Speech carries the win, music barely moves, and decode sits at zero as the control.

Speech encode drops 17-23%, stable across complexity 8, 9, and 10. Music drops about 2%. Decode does not move at all (~1.5%, noise), which confirms the measurement: the kernels are encoder-side, so no change is expected.

Why the split? All three kernels live in the encoder, and two of the three live in SILK, which is what speech runs:

  • silk_inner_product_FLP was plain scalar C, no SSE variant ever existed, so it makes a scalar to AVX2 jump in one step.
  • silk_NSQ_del_dec, the hottest SILK function at the complexity WebRTC uses by default, goes SSE4.1 to AVX2.
  • celt_pitch_xcorr is the lone CELT kernel that improves, so music barely budges.

The WebRTC desktop default is complexity 9 and VoIP is speech-heavy, so the common case gets the big win.

To satisfy my curiosity I also wondered what the hypothetical win over the scalar version is and the relative improvement of AVX2. Quite significant as shown below:

Three-tier decomposition: scalar C, SSE4.1, AVX2
The vectorization win attributed. The speech parts depend on AVX2 while the music improvements were already there with SSE4.1.

And you can push the question one level deeper, because pushing it one level deeper is now free in terms of my time. “Can you split up the improvements and quantify them?” used to buy you an afternoon of profiler wrangling.

The answer: about 96% of the SILK win comes from silk_NSQ_del_dec alone. That is surprising, because silk_inner_product_FLP has the biggest gain on paper. It is nearly irrelevant to the total, because it is a small slice of encode time. NSQ_del_dec is the hot one, so vectorizing it is where the milliseconds actually live. Guessing from the relative gains, I would have pointed at the wrong kernel. I did not have to guess.

The bar moved… again

In the last post, the bar for making a change dropped. This time, the bar for proving and explaining a change dropped. This particular change did not wait two years because it was hard, it was thirty lines the whole time, and the vectorization kernels had been one roll away since 2024.

While in reality this was probably a simple oversight, for the sake of having a story to tell, let’s pretend the change waited because the week after the change was the expensive part: benchmarking, attribution and a sales-y slide deck. The person who knew it was worth doing did not have the week. That week got cheap(er), so it is not a week anymore and I can fit it in a smaller slot. What is left is the same scarce thing it always was: knowing the change is worth making (as demonstrated by Dale Curtis’s 2018 comment), and being able to tell a real win from a flattering graph.

The trailer of the commit message is quite interesting too:

BUG=none

TEST=unittests all pass and bots are happy

This is a relatively low bar but quite common: nobody has time to do it “properly”, but it needs to be done.

The patch, the plan, the reproduction steps and the full results are on the bug. The change landed in Chromium 152. Thank you Dale for writing such a great TODO in 2018, Jakob for doing the heavy lifting on the Opus 1.5 update, and Felicia for the review of the AVX2 change!

One thing to note: if you encode Opus on a server, the relevant vector kernels are SSE and AVX2 on x86, NEON on ARM. If you run a genai voice stack that synthesizes and encodes audio for a bunch of concurrent sessions, the 20% improvement multiplies across every encode and comes straight off your compute bill – even though it is going to be less than 20%. The vectorization kernels are sitting in libopus already. Check if your build has them enabled! The Chromium ones did not, for at least two years.

p.s.: yes MoQ fanboys, this improves WebCodecs too! You are welcome.

{“author”: “Philipp Hancke“, “copyminion”: “Claude”}