DEV Community

Nexus Labs
Nexus Labs

Posted on

Taming the LLM: From Generic to Gold for Go Performance Bottlenecks

Alright folks, let's talk about Large Language Models. Specifically, my recent adventure trying to coerce one into giving me genuinely actionable advice for a gnarly, old Go microservice. We've all seen the headlines, right? "AI optimizes your code!" "Let an LLM find your performance woes!" I thought, 'Great, finally a way to tackle that legacyAuthService beast without diving into a week-long pprof session for every hunch.'

The Problem: When 'Generic Advice' Just Doesn't Cut It

My initial approach was pretty naive, I'll admit. I fired up my favorite LLM (at the time, a souped-up GPT-4 instance) and just asked, "Hey, how can I improve the performance of my Go microservice?" What I got back was... well, predictable. "Optimize your database queries," "Use goroutines efficiently," "Cache results where appropriate." Useful? Sure, in a high-level, 'computer science 101' kind of way. Actionable? Absolutely not.

See, our legacyAuthService handles authentication and authorization for basically everything. It's been around for years, written by multiple teams, and has its share of quirks. We know it's slow, particularly under certain load patterns, but pinpointing where and how to fix it without breaking existing functionality is the real challenge. Telling me to 'optimize DB queries' when I've got three different ORMs and direct database/sql calls mashed together across a dozen services within that monolith, frankly, felt a little insulting. I needed specifics. I needed something I could hand to a junior developer and say, "Go investigate this line or this pattern."

What I Tried: Throwing Data and Hoping for the Best

I quickly realized generic prompts were useless. So, I started throwing more data at it.

  1. Code Snippets: I'd copy-paste a function like legacyAuthService.verifyTokenAndFetchUserPermissions which I suspected was heavy. The LLM would often point out things like redundant map lookups or potential nil pointers, which was cool, but not always the root cause of performance issues.
  2. Raw Profiling Data: I'd run go tool pprof -http=:8080 myapp.cpu.pprof and then copy-paste chunks of the text output (especially the top 10 functions) into the prompt. I'd ask, "Based on this CPU profile, what's the biggest bottleneck?" It could identify the function names, sure, but the suggestions were still quite high-level: "Refactor functionX to reduce CPU usage." Thanks, Captain Obvious.
  3. Different Models & Persona Prompts: I experimented with Gemini Advanced, then Claude 3 Opus, trying to see if a different model had a better 'understanding' of Go code. I even tried those 'Act as a senior Go performance engineer' prompts. While some models were slightly better at parsing code, none of them gave me that 'aha!' moment of specific, tactical advice.

This iterative process of trying to get something useful took me about 3-4 hours over two afternoons. My living room was littered with printouts of flame graphs and half-baked prompt ideas.

What Actually Fixed It: The 'Context-Data-Goal' Sandwich

My breakthrough came when I stopped treating the LLM like a magic oracle and started treating it like a very bright, very literal, junior engineer who needed a lot of structured guidance. I called it the 'Context-Data-Goal' sandwich, and it finally started to produce gold. Here's what it looked like:

1. The Context: Explain the setup, the known symptoms, and the current Go version. "We're running Go 1.21.3 on a Kubernetes cluster. Our legacyAuthService is experiencing high CPU spikes and increased latency under moderate load (around 500 req/s). We suspect contention or inefficient data processing."

2. The Data (Interleaved): Provide the specific information, broken down and guided. Instead of just pasting a pprof dump, I'd say:

"Here's a snippet of our pprof output showing the top CPU consumers:

60.15% 60.15% 12.3s 12.3s myproject/internal/auth.heavyPermissionsCheck
15.20% 75.35% 3.1s 3.1s runtime.scanobject
8.00% 83.35% 1.6s 1.6s go.opentelemetry.io/otel/sdk/trace.(*tracer).Start
...

Based on heavyPermissionsCheck consuming 60% of CPU, here is the code for that function and its direct dependencies:

go
func (s *authService) heavyPermissionsCheck(ctx context.Context, userID string, resource string) ([]Permission, error) {
// ... complex logic involving several DB lookups and map traversals
permissions := make([]Permission, 0, 100) // Could this be pre-allocated differently?
// ...
for _, p := range userRoles {
// This loop runs many times
if p.HasAccess(resource) {
permissions = append(permissions, p)
}
}
// ...
return permissions, nil
}

3. The Goal (Specific Request): Tell it exactly what kind of output you want.

"Given the pprof data and the code for heavyPermissionsCheck, identify specific lines or patterns within this function or its immediate calls that are likely causing the 60% CPU usage. For each identified area, propose concrete refactoring suggestions or alternative algorithms that could reduce CPU cycles, avoiding solutions that require major architectural changes or external libraries unless absolutely necessary. For example, could the make([]Permission, 0, 100) be optimized given typical permission counts?"

This iterative, 'Context-Data-Goal' prompting, particularly with Claude 3 Opus, started yielding fantastic results. Instead of "optimize goroutines," I got: "The repeated p.HasAccess(resource) call within the for loop might be expensive if HasAccess performs complex logic. Consider memoizing HasAccess results per resource within the loop, or pre-filtering userRoles if resource is constant across iterations." That, my friends, is actionable. That's a testable hypothesis. That's gold.

It wasn't magic, and it wasn't instant. It still required me to understand the service well enough to ask smart questions and provide relevant data. But it turned a generic advice-bot into a genuinely useful code-analysis and suggestion engine, saving me hours of tedious manual digging. It's a tool, not a solution, but a powerful one if you know how to wield it.

Top comments (0)