What surprises me is that the original VLC filter does not use SIMD. Sometimes I wonder why I spent my money on a "modern" CPU with a lot of new features that stay completely ignored by most software I run every day.
fenwick67 1 days ago [-]
It surprises me that it runs on the CPU at all, honestly.
ack_complete 23 hours ago [-]
It's also a basic implementation, doing a divide per sample to renormalize and not taking advantage of the symmetry of the kernel.
Even on a GPU, you wouldn't use an algorithm this simple in production. For small radii, leveraging bilinear filtering halves the number of samples. For larger radii, hierarchical pyramid approximations are popular.
ygra 1 days ago [-]
Probably a reflection of the age of that code along with the usefulness of the feature. If no one actually needs the video blurry, then it doesn't matter how efficient the filter is.
pwdisswordfishq 19 hours ago [-]
It surprises me it was even implemented.
boruahpriyanuj2 18 hours ago [-]
[flagged]
martz 1 days ago [-]
The blur is an outlier: a 2007 filter almost nobody uses, so it never got optimized.
The hot paths in VLC (decoding via dav1d/FFmpeg, chroma conversion, surface copies, deinterlacing) are hand-tuned SIMD with runtime CPU dispatch, and most playback runs on the GPU anyway.
rerdavies 14 hours ago [-]
Are large gaussian blurs not used for modern glass effects? Worth pointing out that doing so really requires use of FFT convolutions -- an optimization that has not been implemented in the benchmarks.
Not really the question that this study is addressing though.
theanonymousone 1 days ago [-]
> Two small edits close most of it: compute the loop bounds into locals before the loop,
I always assumed any decent runtime/compiler takes care of such optimisations, and usually leave such things to JDK in my Java code. Is that really not the case?
Rapzid 19 hours ago [-]
Its possible the JIT would have optimized this but that optimization isn't making its way into the native AOT build.
Also possible if it were more idiomatic C# it would have optimized it better. RyuJIT does in fact do BCE and other loop optimizations. It's improving a lot every year too.
The optimization paths from C# though Roslyn and RyuJIT probably don't start with C# written like it were C. Maybe iterating over Spans and slices would have optimized to fewer instructions than raw dogging nested for loops.
kvemkon 22 hours ago [-]
In C++ we have such troubles either. If something looking as constant cannot be 100% proved to be a constant, it cannot be optimized.
You would be astounded how more efficient your program can be if you are doing decent amount of computation or IO.
You can try to understand some of it by doing some measurements and doing some napkin math using the hardware limits like internet bandwidth or IO read/write etc.
RandomOnyx 24 hours ago [-]
Decent ones do. .NET's JIT has historically (before ryujit especially and tbh I haven't looked at .NET much in the past decade) not been decent, general statements about decent compilers never applied to it. It has been playing catch-up lately but apparently it still has a ways to go.
torginus 19 hours ago [-]
That is absolutely not true. Not historically, and not now. .NET's JIT has always been very good, bar a few qualifying statements.
It used to be based on MSVC, with the most expensive optimizations trimmed, things like loop hoisting, devirtualization, generally ones with long runtimes, or ones that required whole program visibility.
But otherwise it was competent, if you wrote code aware of these limitations, it could get very close to even C++, and even on naive code, it was always very competitive (with strange wildcards, that if you used raw pointers, which are an obscure C# feature, you could get code to be even faster, due to the compiler's heritage).
It also helped, that due to MS's nature; C# was meant to be used with MS frameworks, written by people who were aware of the compiler's quirks, which you could learn if you looked at these codebases.
Sure if yo measure against LLVM-compiled static languages, it falls short in certain scenarios, but it's been soldily in the pack of other GC languages like Java or Go, and always has been.
rerdavies 14 hours ago [-]
Thanks for that. I had definitely noticed that the .NET JIT wasn't doing loop hoisting. And that you could get close to C/C++ performance if you did loop hoisting manually. It's useful to get some insight on .NET's actual optimization philosophy.
neerajsi 17 hours ago [-]
Only the ia64 version of the jit was based on msvc. And older versions of aot mode. Not the more typical jit targets.
I believe the new enabling technology for better codegen is tiered compilation, which has only been around for a couple years.
torginus 16 hours ago [-]
The old JIT was a fork of a pared-back version of MSVC; this was for all platforms, except the .NET Compact framework, which was for embedded stuff, and it was its own fork.
There were other weird things like C++/CLI which was a full-fat MSVC fork used to compile C++ either into .NET IL or x86 code which could be embedded into .NET assemblies, but could still call into .NET code.
But what most people think of as .NET (Framework) was definitely using the old MSVC-based JIT
Rapzid 15 hours ago [-]
RyuJIT has had tiered compilation for about 10 years.
to11mtm 15 hours ago [-]
Ehhhhhhh I thought it was a bit of a mishmash, at least before ryujit?
i.e. I think the x64 framework compiler was based on MSVC, but the x86 was a bit of a different beast. That was part of why they did RyuJit in the first place, to help unify the JITs between the large divergence in how they worked [0].
One big thing being, AFAIK the old 32 bit JIT would never use SSE2 outside of some block copy type stuff. They assumed a safe baseline for most other instructions and stuck with that (although, there can be good reasons for this, e.x. consistency with x87 behavior.)
Will also add that the old x64 compiler sometimes really did do some questionable stuff, i.e. when we flipped things to use it we never really saw a perf benefit even when dealing with larger datatypes/etc that should have seen a benefit from wider registers.
RyuJit definitely has fixed that, but... again, the past is a bit hazy.
They have been improving, this is one area however where those C# vs Java flamewars kind of miss out.
While C# the language has much more goodies than Java the language, Java the platform has had much better options than the CLR, because it has been mostly Microsoft and Mono (nowadays almost gone after the acquisition), whereas the Java ecosystem has a plethora of implementations, each one wiht their JIT and GC implementations, even AOT caches since almost three decades now.
Thankfully the outcome of Project Midori, and the whole Secure Future Initiative , has had the side effect of Microsoft becoming more serious about .NET performance, instead of always outsourcing performance workloads to C++ and COM.
zerr 23 hours ago [-]
It never was a priority I believe. As opposed to C, the main target audience of C#/.NET has always been LOB CRUD app shops.
pjmlp 23 hours ago [-]
Kind of, us users of .NET would have liked a Delphi, VB 6 kind of attention to performance, however for many years, Microsoft management had exactly that attitude, expecting that anyone that cared about performance would be writting COM in C++, or DLLs, and consuming those artifacts from .NET.
Finally they changed their approach to .NET, besides Midori, SFI that I mention on a sibling comment, certainly also due to Go and Rust competition.
Note how Azure nowadays does most of their open source contributions in Go and Rust, the rewrites that completly ignored .NET, while the team mentions lack of adoption on .NET related podcasts.
to11mtm 15 hours ago [-]
They had a few 'squirrel' moments.
Silverlight comes to mind, Also the fever dream that was '.NET micro'. I might still have my NetDuino somewhere...
> the main target audience of C#/.NET has always been LOB CRUD app shops.
I do think this has changed over time. There's definitely been improvements to the overall ecosystem from game developers (Cysharp being the most prolific) via both libraries and feedback around the BCL/JIT/etc.
hn_submit 1 days ago [-]
The C# version uses unsafe raw pointers which is practically the same as writing it in C. No surprise then that the outcomes are similar.
pjmlp 23 hours ago [-]
Yeah, the point being that you can write a much safer application, and in a very small section reach out to unsafe code, instead of having 100% unsafe code application, or having to do Python style, "Python libraries" that are actually C libraries.
Rapzid 20 hours ago [-]
You can also do Memory<T> and Span<T> over unmanaged memory to make things.. Safer.
pjmlp 19 hours ago [-]
Sure, but the point still stands versus using 100% raw C.
I only wish that the performance focus was there back in .NET 1.0, with the main architect coming from Delphi, but Microsoft had other priorities.
Also can we please have proper syntax for inline arrays instead of a special purpose attribute? Don't really get that one.
to11mtm 15 hours ago [-]
> Also can we please have proper syntax for inline arrays instead of a special purpose attribute? Don't really get that one.
While I kinda agree (And IMO ask whether having proper syntax would be a stepping stone to other things in future like proper alignment for 16-byte fields so we could sanely do cmpxchg16b), if I had to guess, it's because there's other existing code to handle StructLayoutAttribute, so just doing another attribute versus having a field probably was the 'easiest' way to implement across the different bits of the JIT and GC.
Rohansi 18 hours ago [-]
By inline arrays do you mean within a struct field? You can do that with the `fixed` modifier.
There are much faster algorithms available for Gaussian blur though, running in O(n) rather than O(nσ) for an n pixel image and radius σ. See https://www.ipol.im/pub/art/2013/87/article.pdf for a survey.
schobi 1 days ago [-]
If we look at the example of the FFT blur filter, sure O(n) looks easier and independent of the blur size, but you have an overhead in needing FFT and iFFT. It can be faster for some sigmas, but not for all. So you need different variants depending on sigma and a smooth transition between them.
Then you can start approximating - for large sigmas just using every 10th pixel would work. Looks like MS Teams background blur is doing this, you get a blurry background with aliasing - yay!
Once you add SIMD variants and look at CPU caching behaviour, you build versions for different CPUs. Instead of filtering the whole image for one dimension you might finish blocks locally.
You've now got a dozen variants that need testing and maintenance on different architectures.
I'm reminded of my colleague preaching requirements engineering - what was the goal gain?
anderskaseorg 1 days ago [-]
FFT is O(n log n), not O(n). The better O(n) algorithms are the likes of Deriche, Vliet–Young–Verbeek, or Alvarez–Mazorra.
brookman64k 1 days ago [-]
I would have expected the filters to run as a shader on the GPU which would make them much more efficient.
I remember implementing two-pass gaussian blur in GLSL almost 20 years ago.
bschwindHN 1 days ago [-]
Yep if you want it done quickly, the GPU is the way to go. Especially once you get to 4K resolution and beyond.
Interesting. Even more interesting would have been seeing if/how C# SIMD intrinsics would have improved performance.
moron4hire 19 hours ago [-]
They did do that.
sebazzz 18 hours ago [-]
This is all and nice, but I can debug a native C plugin. Is this possible for an AOT compiled C# plugin? Or can perhaps the C# plugin be also run with the normale JITting runtime being loaded?
platinum95 17 hours ago [-]
Yes, the AOT compiler can emit debug symbols which point to the C# source files, so depending on your debugger you can get it to work.
I previously had to ship a C#/dotnet library in a native C iOS/Android app. It was a PITA but it worked in the end, and I was even able to debug the C# code in XCode (albeit in a limited fashion).
maxlin 1 days ago [-]
Very interesting readup! I have some C# projects I've had in mind to try to compile to AOT. Quite different for complex programs of course, but the perf being very close to C is a great motivator.
mihular 1 days ago [-]
Why would you want them in AOT, though? It's great for startup time and native interoperability, but it might be slower in general due to machine specific optimizations.
mcraiha 1 days ago [-]
Because of Docker. You want the images to be as small as possible.
mihular 2 hours ago [-]
Even that depends. If you use two or more images based on same ie. official .NET image, you have only one .NET image and total images size might be lower.
littlecranky67 1 days ago [-]
I would object, I want my containers to be as fast as possible and the JIT compiles for each CPU arch you run it on (supposedly optimally). Docker image size, who cares?
esafak 17 hours ago [-]
Lens blur is a lot more fun, since each lens is different, and there is an art to it.
18 hours ago [-]
Brian_K_White 1 days ago [-]
Most industries consider 8% a mountain. More than their entire profit margin.
Dylan16807 1 days ago [-]
And 8% milk would taste bad and ruin most recipes.
I don't know why we're comparing completely different categories of number though. In a business context getting a project done a few percent faster, or more accurately one step done a few percent faster, barely affects revenue at all. And saving CPU time matters even less.
grougnax 1 days ago [-]
[dead]
Rendered at 08:25:56 GMT+0000 (Coordinated Universal Time) with Vercel.
What surprises me is that the original VLC filter does not use SIMD. Sometimes I wonder why I spent my money on a "modern" CPU with a lot of new features that stay completely ignored by most software I run every day.
Even on a GPU, you wouldn't use an algorithm this simple in production. For small radii, leveraging bilinear filtering halves the number of samples. For larger radii, hierarchical pyramid approximations are popular.
The hot paths in VLC (decoding via dav1d/FFmpeg, chroma conversion, surface copies, deinterlacing) are hand-tuned SIMD with runtime CPU dispatch, and most playback runs on the GPU anyway.
Not really the question that this study is addressing though.
I always assumed any decent runtime/compiler takes care of such optimisations, and usually leave such things to JDK in my Java code. Is that really not the case?
Also possible if it were more idiomatic C# it would have optimized it better. RyuJIT does in fact do BCE and other loop optimizations. It's improving a lot every year too.
The optimization paths from C# though Roslyn and RyuJIT probably don't start with C# written like it were C. Maybe iterating over Spans and slices would have optimized to fewer instructions than raw dogging nested for loops.
> Incrementing vectors (26.08.2019)
https://news.ycombinator.com/item?id=20800076
You can try to understand some of it by doing some measurements and doing some napkin math using the hardware limits like internet bandwidth or IO read/write etc.
It used to be based on MSVC, with the most expensive optimizations trimmed, things like loop hoisting, devirtualization, generally ones with long runtimes, or ones that required whole program visibility.
But otherwise it was competent, if you wrote code aware of these limitations, it could get very close to even C++, and even on naive code, it was always very competitive (with strange wildcards, that if you used raw pointers, which are an obscure C# feature, you could get code to be even faster, due to the compiler's heritage).
It also helped, that due to MS's nature; C# was meant to be used with MS frameworks, written by people who were aware of the compiler's quirks, which you could learn if you looked at these codebases.
Sure if yo measure against LLVM-compiled static languages, it falls short in certain scenarios, but it's been soldily in the pack of other GC languages like Java or Go, and always has been.
I believe the new enabling technology for better codegen is tiered compilation, which has only been around for a couple years.
There were other weird things like C++/CLI which was a full-fat MSVC fork used to compile C++ either into .NET IL or x86 code which could be embedded into .NET assemblies, but could still call into .NET code.
But what most people think of as .NET (Framework) was definitely using the old MSVC-based JIT
i.e. I think the x64 framework compiler was based on MSVC, but the x86 was a bit of a different beast. That was part of why they did RyuJit in the first place, to help unify the JITs between the large divergence in how they worked [0].
One big thing being, AFAIK the old 32 bit JIT would never use SSE2 outside of some block copy type stuff. They assumed a safe baseline for most other instructions and stuck with that (although, there can be good reasons for this, e.x. consistency with x87 behavior.)
Will also add that the old x64 compiler sometimes really did do some questionable stuff, i.e. when we flipped things to use it we never really saw a perf benefit even when dealing with larger datatypes/etc that should have seen a benefit from wider registers.
RyuJit definitely has fixed that, but... again, the past is a bit hazy.
[0] - https://devblogs.microsoft.com/dotnet/ryujit-the-next-genera...
While C# the language has much more goodies than Java the language, Java the platform has had much better options than the CLR, because it has been mostly Microsoft and Mono (nowadays almost gone after the acquisition), whereas the Java ecosystem has a plethora of implementations, each one wiht their JIT and GC implementations, even AOT caches since almost three decades now.
Thankfully the outcome of Project Midori, and the whole Secure Future Initiative , has had the side effect of Microsoft becoming more serious about .NET performance, instead of always outsourcing performance workloads to C++ and COM.
Finally they changed their approach to .NET, besides Midori, SFI that I mention on a sibling comment, certainly also due to Go and Rust competition.
Note how Azure nowadays does most of their open source contributions in Go and Rust, the rewrites that completly ignored .NET, while the team mentions lack of adoption on .NET related podcasts.
Silverlight comes to mind, Also the fever dream that was '.NET micro'. I might still have my NetDuino somewhere...
> the main target audience of C#/.NET has always been LOB CRUD app shops.
I do think this has changed over time. There's definitely been improvements to the overall ecosystem from game developers (Cysharp being the most prolific) via both libraries and feedback around the BCL/JIT/etc.
I only wish that the performance focus was there back in .NET 1.0, with the main architect coming from Delphi, but Microsoft had other priorities.
Also can we please have proper syntax for inline arrays instead of a special purpose attribute? Don't really get that one.
While I kinda agree (And IMO ask whether having proper syntax would be a stepping stone to other things in future like proper alignment for 16-byte fields so we could sanely do cmpxchg16b), if I had to guess, it's because there's other existing code to handle StructLayoutAttribute, so just doing another attribute versus having a field probably was the 'easiest' way to implement across the different bits of the JIT and GC.
https://endjin.com/blog/csharp-12-inline-arrays
Then you can start approximating - for large sigmas just using every 10th pixel would work. Looks like MS Teams background blur is doing this, you get a blurry background with aliasing - yay!
Once you add SIMD variants and look at CPU caching behaviour, you build versions for different CPUs. Instead of filtering the whole image for one dimension you might finish blocks locally.
You've now got a dozen variants that need testing and maintenance on different architectures.
I'm reminded of my colleague preaching requirements engineering - what was the goal gain?
See here:
https://aras-p.info/blog/2026/10/01/Fast-blur-with-animated-...
I don't know why we're comparing completely different categories of number though. In a business context getting a project done a few percent faster, or more accurately one step done a few percent faster, barely affects revenue at all. And saving CPU time matters even less.