Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
157844 stories
·
33 followers

Virtual vs Override vs Partial in C# Explained with Small Runnable Examples

1 Share
Virtual, override and partial are tiny C# keywords with huge impact on design. This post explains how they work, when to use them, and where bugs appear. Learn the difference between override and new, why sealed override matters, and how partial classes and partial methods keep generated and hand written code happy. Every section comes with short, runnable snippets and real project tips.
Read the whole story
alvinashcraft
44 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

EF Core Query Performance Tips for Everyone

1 Share
EF Core does not make queries fast by default. You do. This guide shows how to cut query time by fetching less with projection, turning on AsNoTracking for reads, avoiding N+1, choosing AsSplitQuery for big includes, paginating, indexing and compiling hot queries. Each concept comes with small runnable C# snippets and clear explanations so you can apply them today without rewriting your app.
Read the whole story
alvinashcraft
44 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Sealed by Default in .NET: when it Shines and when it Bites

1 Share
Sealed looks like a tiny keyword, but it carries serious weight in modern .NET. This post explains what sealed does at class and member level, why the JIT can make sealed code faster, when you should not seal, and how to keep code testable with interfaces. You will see small, runnable examples and practical patterns that balance performance, clarity, and flexibility.
Read the whole story
alvinashcraft
44 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Cognitive Databases, Intelligent Data

1 Share
No longer passive storage and query engines, databases are becoming active, intelligent participants in how modern systems interpret, connect, and act on data. As AI moves deeper into production and enterprises adopt generative and agentic architectures, the database layer is being reshaped to support semantic search, contextual retrieval, and real-time decision-making. Vector databases, semantic indexing, and AI-driven optimization are changing how developers work with both structured and unstructured data, while the line between transactional and analytical systems continues to fade under hybrid workload demands. This report examines these industry shifts in practical terms, exploring how relational, NoSQL, vector, and multi-model systems are coming together to support AI-native applications. Our research, guest thought leadership, and practitioner insights look at how teams are bringing vector search into production, updating architectures for AI workloads, and redesigning data pipelines around semantic and contextual intelligence.
Read the whole story
alvinashcraft
48 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Microsoft expands Azure AI and HPC infrastructure with AMD

1 Share

AI workloads are scaling faster than any single infrastructure approach can support — with more models, new agent-driven workloads and surging compute demand driving the need for greater specialization across the stack. To meet this need, Microsoft continues to evolve Azure’s infrastructure, including expanding its AI fleet with AMD’s most advanced AI and high-performance computing (HPC) solutions.

Our approach to AI infrastructure is designed to support the breadth of how AI systems are built and run. We closely work with industry innovators like AMD as well as our own purpose-built silicon and systems to provide customers with a comprehensive, open and heterogenous platform to achieve the best performance, cost and energy efficiency outcomes.

Building on our close collaboration with AMD, Microsoft is bringing AMD’s latest Helios AI platform and next-generation EPYC datacenter processors to Azure. These technologies will power three upcoming Azure offerings: HDv2 VMs for data processing, HXv2 VMs for electronic design automation (EDA) and ND MI455X v7 VMs for AI inference workloads.

Expanded infrastructure for inference, AI data systems and chip design

YouTube Video

Built for AI data systems — Azure HDv2

CPU infrastructure is essential to the performance and efficiency of modern AI systems. AI accelerators depend on high-density, power-efficient CPU compute to process data, coordinate workloads and keep pipelines running at scale. Without this, training jobs don’t have enough data to learn from, and agents don’t have enough capacity to perform tasks on behalf of customers. Azure HDv2 virtual machines are one of our latest offerings designed from the ground up to eliminate these bottlenecks and empower massive agentic workload adoption.

Co-designed with AMD, HDv2 VMs expand Azure’s portfolio of purpose-built solutions for the most demanding CPU workloads from AI customers, including data preparation, search, reinforcement learning and agent coordination at scale. Featuring nearly 500 physical 6th Gen AMD EPYC CPU cores, 4 terabytes of RAM, 32 terabytes of local NVMe storage and 400 Gbs Azure Boost networking, HDv2 VMs are built for the workload needs of our most demanding AI customers.

Optimized for silicon design and technical computing — Azure HXv2

The AI era has created tremendous need and opportunity for firms developing the silicon products that power this infrastructure. For this reason, Azure HX virtual machines, launched in partnership with AMD in 2023 and featuring AMD’s unique 3D V-cache technology, have seen significant adoption among silicon design firms working to bring more capable and efficient AI silicon to market. Today, we are announcing the next step in our workload optimized journey for these customers, HXv2.

HXv2 virtual machines build on and extend the strengths of HX. They both continue the differentiation Azure offers for RTL simulation workloads by again employing 3D V-cache technology, while offering significant improvements to single threaded performance and memory. HXv2 VMs will feature 176 AMD 6th Gen EPYC CPU cores with a clock frequency of more than 5 GHz, 50% more addressable cache per core and VM sizes with nearly 2 or 4 terabytes of RAM, helping customers optimize their workloads to memory needs.

Azure HXv2 is also designed to support a broader range of technical computing workloads including scientific simulation, engineering analysis and other distributed memory applications. The significantly increased per VM and per core performance, and the inclusion of 800 Gb InfiniBand, enable large-scale MPI-based simulations and make HXv2 an ideal fit for a wide variety of HPC customers.

AMD, a leading HX-series customer, highlights this impact directly:

Engineering teams are pushing the limits of simulation, chip design and scientific computing. At AMD, we experience those demands firsthand as we design future AMD EPYC CPUs and AMD Instinct GPUs. Azure HX is an important platform for scaling complex EDA workloads, and we’re excited about Azure HXv2, which is designed to deliver even greater performance and scalability. We look forward to continuing our collaboration with Microsoft as we help advance infrastructure for the world’s most demanding engineering and scientific workloads.”

— Mark Papermaster, Executive Vice President and CTO, AMD

The HXv2 also leverages Microsoft’s long-standing collaboration to optimize Synopsys AI-powered EDA solutions on Azure:

“As AI compute continues to push the limits of semiconductor design, our collaboration with Microsoft on the Azure HX-series demonstrates a shared vision for enabling customers to deliver next-generation AI systems with precision and scale in accelerated design cycles. These systems have enabled Synopsys customers to reliably and efficiently leverage cloud-based compute, extending EDA workloads beyond traditional infrastructure constraints so they can meet ambitious development schedules while maximizing design quality and delivering dramatic performance gains.”

— Shankar Krishnamoorthy, Chief Product Development Officer, Synopsys

Production-scale AI inference — ND MI455X v7

ND MI455X v7 is designed for the reasoning, search and agentic workloads behind modern AI services. Powered by the AMD Helios rackscale solution, it expands Azure’s infrastructure options for large-scale inference and is designed to deliver strong performance and efficiency for demanding AI workloads.

Together, these new capabilities expand Azure capabilities while giving customers more flexibility to choose the right compute for each unique AI workflow: from inference, to data systems, to chip design. Customer choice is a core design principle built directly into Microsoft Azure, and we’re excited to bring AMD’s most advanced innovations at production scale.

To learn more about Azure’s high-performance computing and AI infrastructure capabilities, visit Azure.com.

Scott Guthrie is responsible for a set of hyperscale cloud computing solutions and services including Azure, Microsoft’s cloud computing platform, generative AI solutions, data platforms and information and cybersecurity. These platforms and services help organizations across the globe solve urgent challenges — and transform for the future.

The post Microsoft expands Azure AI and HPC infrastructure with AMD appeared first on The Official Microsoft Blog.

Read the whole story
alvinashcraft
51 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Escape Analysis in Go – Stack vs. Heap Allocations Explained

1 Share

One of the design choices Google made when developing Go was to abstract memory management away from developers so they could focus on what really matters – writing code. Things like escape analysis and garbage collection are thus automatic, and the Go compiler works in almost mystical ways.

That’s one of the best features of Go, so long as your program works. But when memory issues arise, and you need to demystify the process to optimize it, that’s when the perspective shifts and the mystery is no longer so appealing.

In this article, we’ll explain one of the most confusing performance optimization problems – escape analysis, i.e. how the compiler decides what stays on the stack, and what moves to the heap. We’ll cover what escape analysis is and how it works, what the most common escape cases are and how to inspect them, why inspections might be hard to use, and even how GoLand can perhaps help with that.

What is escape analysis in Go?

Escape analysis is a compiler optimization that determines whether a value can be allocated on the stack or must be moved to the heap. In other words, in Golang, the escape analysis process inspects every value your program creates to answer the question: Can this safely live on the stack, or does something outside the current function still need it after the function returns (and therefore it needs to live on the heap)?

The stack is a per-goroutine region where allocations are significantly cheaper and reclaimed automatically when a function returns, so storage happens fast but is short-lived. The heap is a shared, longer-lived memory space that the garbage collector must track and clean up, so it’s more resource-intensive. Escape analysis is the bridge between the two.

A value is said to “escape” when the compiler can’t prove that it’s done being used by the time the function exits, as explained in the Go documentation. For each value, it asks whether any reference to that value can outlive the function that created it. If the answer is no, the value stays on the stack. If the answer is yes – or if the compiler simply can’t prove the answer is no – the value is allocated on the heap to be safe. The classic example is returning a pointer to a local variable – the function ends, but a reference to that variable lives on, so the value can’t sit on the stack frame that’s about to be discarded. It escapes to the heap instead.

It’s worth pointing out here that escape decisions are not set in stone. They can change depending on how you structure your code, the Go version you’re compiling with, as well as on the environment (OS/architecture), compiler settings, and other optimization decisions like inlining. That’s why you can’t assume a value will or will not always escape in a given context – you need to check every time.

And why should I care?

Unlike in some other languages, in Go you don’t manually choose between stack and heap allocation the way you might with malloc and free in C. Instead, the compiler makes the call. Go also manages memory safety for you, so as to prevent escaped values from being unsafe.

Many developers stop there and never bother with escape analysis. After all, the documentation says that “you don’t need to know”, and if it works, it works, right?

Having said that, you do still have agency and can write code in ways that influence these compiler decisions – in a good way or indeed in a bad way. That’s why an understanding of escape analysis is actually a must-have skill for any Go developer.

Common reasons values escape to the heap

Most of the time, when a value escapes to the heap, it’s for one of a handful of recurring reasons. Recognizing these patterns helps you read compiler output faster and tells you whether a given allocation needs investigation or is simply the best way to run your code. 

It’s important to note that not every escape is a problem – programs with any degree of complexity will inevitably have things living on the heap. The goal here is to recognize the patterns, not to eliminate them all.

Returning pointers

Returning a pointer to a local value is probably the most common cause of an escape. The value is created inside the function, but the caller holds onto a reference after the function returns, so it can’t live on the stack frame that’s being torn down.

func NewUser(name string) *User {
    u := User{Name: name} // u escapes to the heap
    return &u
}

This is safe in Go – the compiler notices that &u outlives NewUser and moves the value to the heap automatically. Whether you should care depends on context. Returning pointers is idiomatic and often the right call for API clarity and readability. The right choice depends on your API design and measured performance impact, not on a blanket rule about avoiding pointers.

Closures and goroutines

Captured variables will escape when a closure or goroutine may outlive the function that created them. The compiler has to assume the captured value is still reachable, so it allocates it on the heap.

func process(data []byte) {
    go func() {
        handle(data) // data may escape: the goroutine can outlive the process
    }()
}

Goroutines are a frequent source of confusion here, precisely because they can keep running after the parent function has returned. From the compiler’s point of view, anything the goroutine touches might be needed indefinitely, so it plays it safe.

Interfaces and dynamic values

Passing a concrete value through an interface can sometimes lead to a heap allocation. This most often occurs in formatting, logging, and interface-based APIs, where values are boxed into an interface{} (any) before they are handled.

func logValue(v int) {
    fmt.Println(v) // v is passed as an interface and may escape
}

However, interface use does not automatically cause heap allocation. Plenty of interface calls don’t allocate at all, and the compiler keeps getting better at this. Treat interfaces as something to check rather than avoid entirely.

Slices, maps, and structs

Values can escape when they’re stored inside a data structure that outlives the current function. If you put a pointer into a map, a slice, or a struct field, and that container lives longer than the function, the stored value has to live just as long.

type Cache struct {
    items map[string]*Item
}

func (c *Cache) Add(key string, it *Item) {
    c.items[key] = it // it escapes: stored in a structure that outlives the call
}

The relationship between the container and the value it holds is crucial here. A slice that never leaves the function may keep its contents on the stack; the same slice returned to a caller or stored in a long-lived struct will push its contents to the heap.

How to check escape analysis in Go

The good news is you don’t really need to remember any common reasons for escapes or guess whether a value escaped or not in a particular instant. The Go compiler flags can tell you that, and in fact, inspecting the compiler’s output is the only reliable way to know what’s happening for sure.

The log covers more than just escapes, though. Alongside allocation decisions, the compiler reports inlining details and other diagnostics, so you get a fairly complete picture of the optimization choices it made for a given build. The downside is that the output isn’t precisely user-friendly or easy to navigate, but we will come back to that later.

How to use compiler flags

The Go compiler surfaces escape analysis information through the -gcflags debug flag with the -m option:

go build -gcflags="-m" ./...

The -m flag asks the compiler to print its optimization decisions, including whether a value escaped. The output looks roughly like this:

./user.go:6:2: moved to heap: u
./user.go:7:9: &u escapes to heap

You can pass -m twice (-gcflags="-m -m") for more detailed reasoning, though that quickly becomes verbose. There are more flag variations, but -gcflags="-m" is the one you’ll probably reach for most.

As you can see, the output is keyed by file, line, and column, and escape analysis can be buried among other comments. This means the real work is mapping each message back to the relevant source code so you can understand it in context.

Why it’s hard to work with escape analysis logs

While compiler flags are the only way to reliably see what decisions the compiler made, they are arguably not the most ergonomic one. The report may be perfectly readable when you work with a small file, but in larger projects and with daily use, it can quickly become frustrating. No wonder then that it’s a heavily underutilized feature of the Go SDK.

A few common pain points have come up in our discussions with Go developers:

  • The output is noisy – a real build prints escape decisions, inlining notes, and other diagnostics all in one place, and most of it isn’t what they’re looking for at the moment.
  • Messages are hard to connect to the source – each line is tagged with a file, line, and column, but they still have to open that file and find the right spot.
  • They have to constantly switch context – reading a message in the terminal, then jumping to the editor to see the code, then back again. This disrupts their concentration and slows the investigation.
  • Not every escaping value is worth optimizing, but the output treats every allocation equally. Meanwhile, most of them don’t matter for performance, and it’s hard to separate signal from noise.

None of this makes command-line escape analysis bad. It’s a genuinely powerful diagnostic that’s just not always convenient, especially when you’re trying to answer a focused question inside a large codebase. Because escape analysis has been locked behind obscure compiler flags and hard-to-parse logs, it’s become a niche practice even among experienced Go developers. That’s why our GoLand team has designed a tool that lowers the barrier to entry and bridges the gap between “powerful” and “convenient”.

How GoLand helps with escape analysis

The GoLand escape analysis support that arrived in the 2026.2 release was built to address the pain points we’d heard from developers. Under the hood, the tool largely does what you would do manually, running the go build command with the -gcflags="-m -m" flag. (To be precise, GoLand runs -gcflags="-m=2 -json=0,<path>", since we found that storing logs in JSON format provides a more structured and stable output).

But the tool now also adds a layer that parses that raw gcflags output and brings it directly into the editor, so you can stay close to your code while investigating allocation decisions instead of bouncing between the terminal and your files.

Running the escape analysis tool

The workflow is pretty straightforward. You open the Go Optimization window, choose Escape analysis, pick a scope, and run it.

You can analyze a single file or a whole package – the file-scoped option is handy for tightly focused units of code, such as an individual AWS Lambda handler, where you only care about one function’s allocations.

You can also choose which message types to show (see: How to read escape messages) and set environment variables for the Go process before running. The most frequently used are compiler flags (goflags) – on top of the standard -m, you might also be interested in -N (disables compiler optimizations) and -l (disables function inlining). The values of the GOARCH and GOOS environment variables can also affect your output, as some compiler decisions are target-dependent and can affect inlining, allocation decisions, and the diagnostics reported by gcflags.

Working with the output

Once the analysis finishes, you’ll find the results where they’re most useful:

  • In the editor: Gutter markers with escape messages appear right next to the lines they describe. If several messages belong to one line, the marker will show you the count. Also, hovering over the gutter marker will show you the compiler message and the escape flow. Hovering over a function name will show the escape results for that function, so you no longer have to match line numbers by hand.
  • In Go Optimization tools: This tool window lists the results by file, function and/or category, and then message type. You can also filter the logs by message type to cut through the noise. Click on any result to jump straight to the corresponding line in the editor.
  • Views: By default, the Escape analysis tool shows the output as a parsed tree. If you prefer the unprocessed output from the compiler’s command, the console output view shows it raw. Even there, the lines are clickable and take you to the right place in your code.

Comparing files

After you make changes to your code, you can rerun the analysis and compare results in separate tabs to see whether the allocation actually moved off the heap. This is important for iterative work and making sure the changes you make actually move the needle. If you’re already used to profiling your programs, this is a natural extension of that process. And if not, you can read more on how to profile Go code with GoLand to get a more detailed picture.

How to read escape messages

The console messages are generic. The two you’ll see most often are escapes to heap and moved to heap. Both indicate that a value couldn’t stay on the stack. Others describe inlining and parameter behavior.

Treat these as the diagnostic signals that they are, not as refactoring instructions. A moved to heap message is just information about what the compiler did. Whether it’s worth acting on depends entirely on how that affects performance.

Here are the message types that the GoLand tool surfaces and what they mean – they map directly to the compiler reports:

MessageWhat it means
Escape to HeapA value must be allocated on the heap because it’s still needed after the function returns.
Moved to HeapThe compiler couldn’t guarantee the value is no longer needed after the function returned, so it allocated it on the heap.
Leak ParamA function parameter escapes the current function and may need to stay valid after it returns.
Can InlineA function is small and simple enough that the compiler can (but doesn’t have to) replace calls to it with its body.
Inlining CallThe compiler actually inlined a specific call.
OtherAdditional compiler diagnostics related to escape and optimization decisions.

To read more about this and see examples, go to the GoLand documentation.

Escape analysis and performance

Escape analysis matters for performance because heap allocations aren’t free. Every value on the heap generates more work for the garbage collector to track and reclaim, and the allocation itself carries overhead that stack allocation doesn’t. If you reduce unnecessary heap allocations on a hot path, you can potentially meaningfully cut both GC pressure and latency.

That said, heap allocation is normal and frequently necessary in Go. Plenty of values should live on the heap, and trying to force everything onto the stack is a losing game that hurts your code’s readability for little to no gain. Escape analysis is most valuable in specific places: hot paths, tight loops, high-throughput services, serialization and deserialization code, and latency-sensitive workflows. Outside those areas, an escaping value is usually just an escaping value. In other words, the old adage about premature optimization applies to escape analysis like nowhere else, and you should only focus on the proverbial 3%.

The single most important habit is to measure. Escape analysis tells you what the compiler decided, but it doesn’t tell you whether that decision is hurting you – only benchmarks and profiling can do that. Use escape analysis alongside benchmarks and profiling in Go, and always measure before and after a change to see whether it actually helped. Escape analysis is one performance input, not a complete strategy on its own, and not every escaping value is worth a developer’s time.

Best practices for working with escape analysis

Finally, here’s a short, practical checklist for using escape analysis well in real projects:

  1. Start with measurement. Use profiling and benchmarks to find allocations that actually matter before you open the escape analysis logs. Don’t optimize unquestioningly.
  2. Focus on hot paths. Concentrate your attention on tight loops, high-throughput code, and latency-sensitive sections. Apart from these instances, escapes rarely justify the effort of avoiding them.
  3. Understand why the value escaped. Read the compiler message and the escape flow so you’re fixing the cause, not the symptom.
  4. Avoid unnecessary micro-optimizations. Treat heap allocation as a signal worth examining, not as an automatic bug to be eliminated.
  5. Protect readability and design. Don’t contort an API or sacrifice clarity to shave an allocation that doesn’t show up in your benchmarks. Maintainable code always wins over clever code.
  6. Verify your changes. Rerun the analysis and re-measure to confirm that a change did what you intended.

You may also be interested in Go’s official Guide to the Go Garbage Collector, which has an optimization guide for the entire GC, including how to eliminate heap allocations with escape analysis.

FAQ

Does escape analysis improve Go app performance?

Yes and no. When speaking of escape analysis as a part of the compilation process, it was designed to ensure optimal performance by prioritizing fast allocation and reducing garbage collection pressure, both of which improve your app’s performance.

However, as a developer tool, escape analysis is just a diagnostic, not an optimization that you turn on. What can improve performance is using its output to spot avoidable heap allocations on hot paths and adjusting your code accordingly. With code that isn’t performance-critical, acting on escape results usually changes nothing measurable.

Is escape analysis the same as profiling?

No. Profiling tells you where your program spends time or memory at runtime. Escape analysis is a compile-time snapshot of where values are allocated and why. They’re complementary: Profiling tells you where to look, and escape analysis helps you understand why the allocations are occurring in those locations.

Can the results of an escape analysis change between Go versions?

Yes. Escape decisions depend on the compiler, and the Go team improves its analysis and inlining over time, as they did in the 1.25 and 1.26 releases. A value that escapes in one Go version may stay on the stack when using another.

Should developers avoid pointers to reduce heap allocations?

Not as a rule. Returning or passing pointers can cause values to escape, but pointers are idiomatic and often the clearest choice. Avoiding them everywhere harms readability and can even hurt performance if large values have to be copied. Decide based on API design and measured impact, and use escape analysis to check rather than to enforce a blanket policy.

Do interfaces always cause values to escape?

No. Passing values through interfaces can contribute to heap allocation in some cases – often around formatting and logging – but it doesn’t always, and the compiler keeps getting better at avoiding it. Interface boundaries are worth keeping an eye on in the compiler output, but they’re not a guaranteed source of escapes.

When should I care about Go escape analysis?

When you have a performance-sensitive path and evidence that allocations are part of the problem. If profiling points to allocation pressure in a hot loop, a high-throughput service, or serialization code, escape analysis helps you understand and address it. For everyday code that meets its performance goals, you can let the compiler do its job and move on as Go intended.

Read the whole story
alvinashcraft
56 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories