V-Ray 5 render: aerial futuristic cityscape with rooftop gardens and glowing towers.
Dian Nikolov

Dian Nikolov

Published: September 21, 2026  •  17 min read

ICX and Profile-Guided Optimization in V-Ray

Together with Dian Nikolov, Software Developer for V-Ray for Maya at Chaos, this article was written with contributions from Teodor Petrov, Software Developer at Chaos's Innovation Lab; Kristiyan Tsaklev, former Software Automation Developer at Chaos; and Leonhard Rannabauer, Application Engineer at Intel.

The challenge

At Chaos, optimization is the heartbeat of our development cycle. Our goal is to ensure that every scene, no matter how complex, renders with maximum efficiency. But deciding where to focus our effort is rarely straightforward. We can redesign entire algorithms, come up with more efficient data storage or access patterns, use clever maths, or go really low-level and use more efficient CPU instructions. And we do all of that. But we can’t step through the code with a debugger or read through thousands of lines of code, hoping to spot a performance bottleneck.

We need a map.

We rely on the Intel® VTune™ Profiler to provide that map, identifying exactly where the hardware is struggling - be it inefficient memory access, cache misses, or stalled CPU pipelines.

 

VTune Memory Access summary: 30.6s elapsed time, 47.3% DRAM bound on P-core

Figure 1. A profiling summary of a scene with deep output. RAM access is a major bottleneck, and a detailed look at the relevant source code reveals problematic traversal and operations with a linked list.

 

Intel VTune Bottom-up hotspots view with per-thread CPU time timeline

Figure 2. A detailed performance profile of a scene with poor CPU utilization. A time slice with particularly bad utilization is isolated, and the report reveals a thread-blocking function where the majority of the CPU time is spent.

 

Yet, even with clear profiling data, we often hit a wall. We consistently find performance issues that are notoriously difficult, if not impossible, to resolve manually:

  • Branch Predictions: How often does a specific conditional statement actually trigger? A developer might have a hunch, but the compiler usually has to guess. If the compiler guesses wrong, the CPU wastes cycles on the wrong path.
  • Hot Code Layout: Code that is well laid out to fit into the instruction cache is faster. However, manually rearranging the code is hard, and constant refactoring for every small gain is a recipe for unmaintainable, buggy software. Efficient code layout improves instruction cache performance and reduces the pressure on the Translation Look-aside Buffer (TLB) - the high-speed "index" the CPU uses to translate virtual memory addresses into physical ones. If we don’t optimize this layout, the CPU wastes time searching for data (see Figures 3.b and 3.c).
  • Devirtualization: Virtual calls are a pillar of clean, object-oriented code. For example, instead of writing separate code for intersecting every object, we can simply write shape->intersect(). The program figures out at runtime if that shape is a Sphere or a Box. This flexibility is excellent for development, but it comes at a cost - the CPU must perform a lookup for every single call to find the right function, often being 1.25x to 5x slower than regular function calls. Worse, virtual calls block function inlining, a common compiler optimization where the code inside a function is "pasted" directly where it’s called, improving performance by avoiding jumps between functions. Manually "devirtualizing", essentially rewriting the code to tell the compiler exactly which function to call, would be a nightmare, and it defeats the purpose of flexible design.

While we still do static manual optimizations, manually optimizing for accurate branch predictions, better code layout, and devirtualization are practically impossible. This is why we started looking for tools that could do these types of optimizations automatically. That search is what led us to the ICX compiler and the intelligence of Profile-Guided Optimization (PGO). The following is our journey from this realization to switching to the Intel ICX compiler and implementing a profile-guided optimization pipeline, and how it benefits V-Ray.

The traditional optimization baseline

Traditionally, compilers rely on “static analysis” to optimize code. They examine a representation of the program - often something close to assembly language, such as LLVM IR - and apply general heuristics to squeeze out performance. They might, for example, unroll loops to allow the CPU to process multiple arithmetic operations simultaneously via SIMD arrays, rather than handling them one by one. Think of it like checking off items on a to-do list. Instead of doing one task, looping back, doing the next, then repeating, the compiler "unfolds" the loop to process several items in a single pass, saving the CPU from constantly looping back to the start. If the tasks are independent, it can go even further with auto-vectorization, essentially telling the CPU to do multiple calculations all at once.

These traditional methods are incredibly effective and form the essential backbone of modern software performance. Sometimes, however, the best approach is unknown to these methods. They don't know the difference between a function used billions of times a second and a rare feature that is almost never triggered. In other words, they are blind to how the code behaves in the wild.

This brings us to the fundamental problem: despite being very effective, static optimization can only take us so far. To get the next level of performance, we need to start observing what the program actually does during execution.

What is profile-guided optimization?

Profile-guided optimization addresses this fundamental problem by feeding the compiler with a profile of how the application is executed on the target hardware. This allows already existing compiler optimizations to be performed more accurately, and it allows to add novel optimization techniques:

  • Function inlining reduces the number of executed instructions, as it allows dropping the preamble and epilogue of a function, which are normally required to prepare and recover stacks and registers before a call. Inlining functions and merging their instructions also enables default compiler optimizations to be executed across function boundaries. While inlining is an already established optimization technique without PGO, profiles contain call stacks that allow us to predict more accurately where inlining of functions leads to better performance and where it would introduce a new bottleneck.
  • Block reordering - profiles contain information about which basic blocks (contiguously executed instructions) in a binary are called most frequently. Cold instruction blocks are separated from hot ones, and the hot blocks are placed close to each other. This improves the spatial locality of instructions in a binary. Higher spatial locality means better usage of instruction caches and Translation Look-aside Buffers.
  • The same effect is targeted in Conditional Branch optimization. The more frequent outcome of a branch is set to be the fall-through case (or non-taken case) of a branch. Its instructions are put closer to the rest of the execution, and again spatial locality is increased.

Compiling with profile-guided optimizations

PGO tries to understand which parts of the code are important, yet expensive and executed often enough to matter.

Compiling a program using PGO is done in 3 steps:

  1. Compile the program with instrumentation added by the compiler (think of it like additional counters and logs), needed for the profile generation in the next step.
  2. Run the program on a representative set of input data and gather data on its execution to create a profile.
  3. Use the profile to optimize the program and compile it again.

While this is an accurate method of creating a profile, it has some downsides:

  • Running the instrumented build could take a long time; the instructions added for instrumentation significantly slow down the application. A workload that takes 5 min in a normal build can easily turn into hours.
  • If the application has time-critical components, the slower instrumented execution can make the profile not representative. For example, if an application offloads work to the GPU, slowing down the application on the CPU might lead to new CPU bottlenecks that appear in the profile.

Finally, the instrumentation must be repeated after each code change: the data in a profile from instrumented PGO is strictly related to the original code base.

ICX and HwPGO

Intel's HwPGO aims at enabling PGO for a wide variety of client applications and addressing these issues. HwPGO switches from instrumenting the code to sampling performance events for the original code base. The same information on function call frequencies, branch probabilities, and hot blocks can be accurately approximated from Intel's Last Branch Records. LBR evenly creates samples of 32 contiguously taken branches during program execution, allowing to create a close approximation of the control-flow-graph. This approach addresses the obstacles in front of PGO:

  • The overhead of sampling LBR is negligible compared to the workload, so profiles can be created much faster than for instrumented PGO and represent realistic usage of the application.
  • Branches recorded in LBR data can be mapped between various builds of the same application with code changes. This, in turn, allows profiles to be reused between builds and gives a similar performance gain. It is enough to update the profile for every major revision of the code.

V-Ray is a complex program. There are many features implemented, and most of them are rarely enabled all at once. In particular, some parts are used in very specific situations.

Many of the features are implemented using conditional branches (e.g., if/else statements) in the code and these conditionals are checked against very often, but they go the same way all the time (taken or not taken). For example, code like:

if (<feature_X is ON>) ...

could be checked on each ray hit (which could be tens of billions or more times during rendering), and the branch would never be entered for the majority of the scenes we render. We want the compiler to assume such branches would most likely not be taken and consider them for optimization.

The PGO feature in the compiler allows us to sample all these conditionals and get a list of the ones that go only one way. With this information, the compiler can recompile the program in a way that allows it to run faster on modern CPUs. In particular, it can lay out the code in such a way that the code that follows the conditional on the correct branch is directly after the conditional, which results in using the instruction cache more efficiently.

ICX and PGO in V-Ray

The beginning

The journey began in 2022 with Dian Nikolov, a developer for V-Ray core and V-Ray for Maya, who was on a quest to deepen his understanding of low-level hardware optimizations. He often reviews or works on performance-critical features, so a more intimate knowledge of how software interacts with modern silicon would have been of great use.

Armed with insights from Algorithms for Modern Hardware, Dian began to see a clear opportunity for performance improvements in V-Ray.

 

VTune microarchitecture exploration: CPI rate 1.144, 32.8% front-end bound

Figure 3.a. Microarchitecture analysis of an interior scene render. The analysis looks similar to this for the majority of the production scenes we render.

 

VTune ICache Misses metric detail, 5.8% of clockticks, flagged issue

Figure 3.b. The analysis shows that instruction cache misses are a significant bottleneck and profile-guided optimization can potentially help.

 

VTune ITLB Misses metric detail, 6.4% of clockticks, flagged issue

Figure 3.c. The analysis shows that instruction TLB misses are another significant bottleneck where profile-guided optimization can potentially help.

 

This was a path previously explored by others at Chaos, though often abandoned when the results proved elusive. Dian was determined to press ahead and created an experimental setup with the MSVC compiler. His initial results were a rollercoaster - some scenes would see a 10% speedup, but others would see an equal slowdown. Hoping to smooth out the inconsistencies, he attempted to combine profiles from multiple scenes, but the benefits had evaporated, leaving him with results similar to the unoptimized version, and the experiments were shelved.

A year or so later, during one of our regular meetings with Intel, one of their application engineers, Leonhard Rannabauer, brought up V-Ray’s performance profile and how we could potentially benefit from profile-guided optimizations.

We agreed to give it another try, even though Leonhard confirmed that there was nothing really wrong with the initial MSVC PGO setup. This time around, we would try a setup with the ICX compiler and its HwPGO functionality.

Initial Integration of ICX and PGO in V-Ray

The initial integration of ICX into V-Ray was driven by Leonhard with Dian's support. We knew this transition would be a significant undertaking, and our early efforts immediately revealed three primary obstacles:

First, moving from MSVC to LLVM with Clang in ICX proved to be a challenging shift. MSVC had long been our stable foundation when compiling for Windows - a robust compiler that tolerated aliasing or other undefined behaviors, and even loose syntax. Clang's more aggressive optimization logic would reject this, and as a consequence, a large portion of our initial effort was spent identifying and resolving code sections that produce different instructions for MSVC and Clang.

Second, V-Ray depends on many open source libraries. Some libraries do not support Clang and also have the same issues as V-Ray when transitioning from MSVC to Clang. We decided to keep the non-performance-critical libraries compiled as MSVC binaries, which meant we had to carefully navigate ABI compatibility issues when linking them against our Clang-based V-Ray objects.

While simple examples worked seamlessly, more complex code bases like V-Ray turned out to be challenging. One example is implicit alignment, which MSVC performs for large static variables in OpenVDB - Clang expects a directive hinting at this alignment, while MSVC implicitly assumes that a class over a certain size is aligned. In these instances, the Intel team was instrumental in helping us bridge the gap, particularly by solving complex, long-standing issues like Asynchronous Exception support in LLVM.

Finally, we were adopting ICX while it was still on its bleeding edge. The compiler’s aggressive optimizations on top of Clang, which were key to our performance goals, occasionally resulted in stability issues during early iterations.

In the end, V-Ray not only got faster, but moving it to ICX forced us to improve the quality of our own codebase, and at the same time ICX turned into a more mature and robust compiler.

Mini Hackathon in Sofia

At that point, the results were promising, but we still had a long road ahead of us. There were major blocking issues with integrating ICX and HwPGO into our build system, not to mention the lack of Linux support.

In April 2024, a visit from the Intel team - including Leonhard - provided a rare opportunity for hands-on collaboration. We held a high-intensity hackathon focused entirely on unblocking the integration.

Dian and Leonhard turned their attention to the Windows build, systematically stripping away legacy hacks and stabilizing the crashes from our earlier iterations. Simultaneously, Teodor Petrov and Dimitar Toshev, two of our capable Linux developers, tackled Linux support, working to integrate ICX and PGO into our build system. We received just the help we needed with some of the more obscure issues of adding support for a new compiler from Kamen Lilov - the architect of our CMake build system.

By the end of the hackathon, we were exhausted, but the results were undeniable. The tests were passing, the builds were stable, we had Linux support, and we had finally cleared the path for production-ready integration. What had previously been an experimental effort for V-Ray Standalone now had the momentum to expand into the V-Ray integrations like 3ds Max and Maya, and our full Jenkins automation pipelines.

Making ICX and PGO production-ready

Transitioning to ICX on Linux was surprisingly smooth, since we were already using Clang there. Windows, however, was an entirely different type of challenge. We had experimental Clang builds of V-Ray Standalone and some versions of V-Ray for 3ds Max - a starting point that quickly unraveled as we pushed further. We had compiler and linker errors for 3rd-party dependencies like USD, MaterialX, OpenVDB, and the V-Ray core SDKs for 3ds Max and Maya. Integration tests were failing, and we had ICX/Clang compiler bugs. 3rd-party libraries mostly required simple header adjustments, but some needed a full rebuild. The failing integration tests revealed a few actual bugs, and thanks to our existing set of more than 6,000 test scenes we were able to quickly isolate and fix those, making the ICX integration much more solid.

Intel’s responsiveness proved equally critical - they rapidly addressed the compiler bugs we faced, ensuring we weren't left waiting on external fixes.

At this stage, we had established the basic CMake toggles for profile generation and usage, and we were hoping that our automation team would figure out the rest. We discovered that a lean selection of just a few scenes, specifically chosen for their diverse feature usage, was sufficient to deliver consistent, meaningful speedups. This was a valuable lesson in efficiency: rather than attempting to brute-force the compiler with massive datasets, we found that a representative, carefully curated selection provided the optimal balance of performance gain and build simplicity.

PGO automation: closing the loop

Transitioning PGO from an experimental proof-of-concept to a core component of our CI/CD pipeline required more than just compiler flags - it required automation. Since codebases evolve rapidly, static profiles quickly become obsolete. We needed a system that could keep our profiles as fresh as our code. More importantly, we needed to not rely on manually performing the many steps needed for the setup - we needed automation.

We established a weekly cadence: every Saturday, our build system executes a dedicated "profile generation" pass (WITH_PGO=HW_GEN) across our representative set of scenes. The resulting HwPGO artifacts are uploaded to our internal package management system and tracked via Git tags (e.g., pgo/<version>). This tagging strategy gives us a clear audit trail, allowing us to trace any optimized build back to the exact profile version used.

We start by listing existing “pgo” tags with git tag --list pgo/*

Then, we get the largest number and add 1 to it to get the next one.

To reserve a version number, we tag the commit before the build starts, creating a brief window where the tag exists but the profile artifact is not yet available.

This enables us to have a build option to use a premade profile for HwPGO-optimized builds (WITH_PGO=HW_USE) by just passing the version number.

Ideally, we want the most recent tag, as it contains the most relevant profile.

Looking up the most relevant version number by hand is not very convenient, though, so we implemented an “auto” mode that finds the version from the closest tag:

git describe --tags --no-abbrev --match=pgo/* HEAD

This is almost perfect - we could now run builds from a commit which will be tagged in the future. Since we generate new profiles on Saturdays, building on Friday night will automatically select last week’s profile. Assuming no one pushes on Saturday, the same commit will be used for profile generation. Builds from the same commit after Saturday will use the new profile. To make the profile selection more deterministic, we can ignore the current commit if it is tagged. This solves the problem from earlier by ignoring the current commit if it is already tagged, preventing the system from selecting a nonexistent profile.

For internal builds with HwPGO, we first check if HEAD is tagged with:

git describe --tags --no-abbrev --match=pgo/* --exact-match HEAD

If it is (e.g., pgo/42), we search for another tag with:

git describe --tags --no-abbrev --match=pgo/* --exclude pgo/42 HEAD

For release builds, we enforce a strict requirement: the system must verify that a profile was generated for that specific release commit. Implementing this tag-based workflow ensures that every build is as efficient as the last.

Performance results and official releases

With all of this work done, we decided that the integration is now in a pretty good spot and we would release the first official V-Ray builds with ICX. Those were the Windows builds of V-Ray 7 for Cinema 4D, released on Jan 15, 2025. On July 1 2026, we released V-Ray 7, update 4 for Maya, also compiled with ICX for Windows. At the time of writing, there are no outstanding issues related to the ICX integration. This is very promising and a good sign for our next steps.

For the final performance results, we measured the render time differences for multiple production scenes between two builds built from the same commit. The builds were MSVC vs ICX+HwPGO on Windows and Clang vs ICX+HwPGO on Linux. We benchmarked the performance with V-Ray Standalone on a set of more than 50 scenes. The render time was averaged across 5 runs on the same scene. For illustration, we’ve included a graph with a part of the results below.

 

Render benchmark: MSVC vs ICX+HwPGO compiler times on Intel i9-13900K

Fig. 4.a. A selection of test scenes used to benchmark performance with V-Ray Standalone built with MSVC and ICX+HwPGO on an Intel CPU on Windows.

 

Render benchmark: MSVC vs ICX+HwPGO compiler times on AMD Threadripper 7995WX

Fig. 4.b. A selection of test scenes used to benchmark performance with V-Ray Standalone built with MSVC and ICX+HwPGO on an AMD CPU on Windows.

 

Render benchmark: Clang vs ICX+HwPGO compiler times on AMD Ryzen 7 1800X

Fig. 4.c. A selection of test scenes used to benchmark performance with V-Ray Standalone built with Clang and ICX+HwPGO on an AMD CPU on Linux.

 

Aerial view of a neon-lit cyberpunk cityscape at night, 3D render

Fig. 5.a. An example image for the “City Tower” scene, used in our benchmarks.

 

Volcanic eruption with fire and ash plumes over rugged mountain terrain

Fig. 5.b. An example image for the “Volcano” scene, used in our benchmarks.

 

"We have collaborated extensively with Chaos to incorporate the Intel® oneAPI DPC++/C++ Compiler, featuring hardware profile-guided optimizations, into V-Ray. The release of this integration, and the resulting performance enhancements for end-users, have exceeded our expectations. We're delighted Chaos is at the forefront of these innovations and look forward to continuing to push the limits of 3D rendering with them in the future."

Mike Chynoweth - Intel Fellow, Client Computing Group Chief Software Performance Architect

What’s next?

At the time of writing, V-Ray for Maya, Houdini, Cinema 4D and Blender, as well as V-Ray Standalone, V-Ray App SDK and the V-Ray Hydra delegate, are compiled with ICX and PGO for Windows and Linux. In the future, we will migrate the rest of the V-Ray integrations.

While we can use the Visual Studio debugger for ICX builds, we still cannot switch our day-to-day Windows development to ICX because of a few blocking issues when compiling through Visual Studio.

Intel is continuously improving the HwPGO optimizations and works on new approaches based on the experience gained with V-Ray. New optimizations are based on the misprediction information of LBR to optimize the control flow, and memory layout optimizations are explored based on data linear addresses in Intel PEBS.

References

Virtual calls benchmarks, https://www.complang.tuwien.ac.at/forth/threading/

Algorithms for Modern Hardware, https://en.algorithmica.org/hpc/

Profile-guided optimization, https://en.wikipedia.org/wiki/Profile-guided_optimization

Profile-guided optimizations, https://learn.microsoft.com/en-us/cpp/build/profile-guided-optimizations?view=msvc-170

Intel® oneAPI DPC++/C++ Compiler, https://www.intel.com/content/www/us/en/developer/tools/oneapi/dpc-compiler.html

Intel® VTune™ Profiler, https://www.intel.com/content/www/us/en/developer/tools/oneapi/vtune-profiler.html

Clang<->MSVC compatibility, https://clang.llvm.org/docs/MSVCCompatibility.html

Authors

Dian Nikolov, Software Developer V-Ray for Maya, Chaos: dian.nikolov@chaos.com

Teodor Petrov, Software Developer, Innovation Lab, Chaos: teodor.petrov@chaos.com

Kristiyan Tsaklev, former Software Automation Developer, Chaos

Leonhard Rannabauer, Application Engineer, Intel: leonhard.rannabauer@intel.com

Ready to try Chaos products?

Start your free trial today and explore all features.

Share
Dian Nikolov
Dian Nikolov

Dian has been a Software Developer at Chaos since 2017. He loves working on light transport algorithms, material appearance, GPU rendering and low level optimizations. His contributions throughout the years include various material features and implementations, volumetric and fog improvements, V-Ray GPU and Vantage work, progressive caustics and many others. You can get in touch with him at dian.nikolov@chaos.com.