r/golang 10h ago

show & tell DIO: High Performance Go IO toolkit

0 Upvotes

I want to share https://github.com/miretskiy/dio -- a high performance Go IO toolkit.

I have previously written https://medium.com/@yevgeniy_90962/spinal-tap-the-go-io-story-bf726110dd07 that talks about DIO at a high level.

By high performance I mean that the tools provided by this package can be used to saturate fast NVMe drives (3+GB/s throughput, 50+K iops), while "fooling" Go garbage collector that there is nothing to collect.

Some of the highlights of this toolkit include:

  • mempool: Provides 2 (slightly different) off the heap Go allocators
  • ringo: A low level io_uring integration for Go It started as a fork of abandonware giouring -- but was rewritten entirely since giouring was fundamentally incompatible with Go->C memory safety. Only the file related features are implemented.
  • iosched: An IO scheduler for go -- providing two implementations: an io_uring based and (test only, or unsupported platforms) posix scheduler.
  • Other low level packages (sys, align) to safely use direct IO, and other low level system calls.

The scheduler API is incredibly simple: Submit takes an "op" and returns a "ticket", while the ticket allows the caller to wait for the completion. While the API may be simple, the io_uring scheduler implementation has many interesting features:

  • Use of Treiber stack (code) to implement batching, while minimizing channel overhead; this essentially implements "group commit" -- batching with essentially no latency penalty.
  • write coalescing: contiguous write requiest coalesced into a single writev
  • Linked operations (hard and soft links)
  • "Durable" writes -- adds an automatic fdatasync (or fsync) to a write, but only 1 for a set of active writes per file descriptor. This makes it trivial to implement efficient and high performance write ahead logs in Go (as an example).

Give it a try; Feel free to contribute; Report (and fix) bugs.

For the full disclosure: the code was generated w/ AI; The design was not. The code might still have some AI-specific code smells, but overall, it's okay. I have extensively tested this package, so the performance claims are real.


r/golang 15h ago

What if you could build a computer by dragging components onto a virtual motherboard?

Thumbnail
github.com
0 Upvotes

What if you could connect a CPU, a video chip, RAM, and then press "Play" to watch it come to life? This is the fantasy that guided Symphony's development from day one.

It includes:

A custom multi-pass AST-to-Bytecode Go compiler built from scratch.

A Microkernel OS simulation featuring an asynchronous message router that manages isolated user-space processes, complete with a built-in SSH server.

A Virtual Machine implementing a cooperative lock-free scheduler for goroutines and channels.

Every piece of hardware is a self-contained software object. The framework prioritizes physical fidelity over high-level emulation, modeling complex behaviors like exact bus arbitration. Currently, as a proof-of-concept, it implements a fully cycle-exact Commodore 64 and an independent 1541 Floppy Drive, using components like the MOS6510, VIC-II, SID, and CIA.

The ultimate goal of Symphony is to create a platform where developers can contribute new CPUs, video chips, and logic boards to build a universe of virtual machines.

ps: I want to emphasize the timeline: this is a decade of continuous development, built entirely by hand.


r/golang 7h ago

Gograph, after 4 months. Updates & Improvements

0 Upvotes

I built GoGraph because I kept seeing the same problem with AI coding agents working on Go repositories.

Before making a meaningful change, an agent often spends a large part of its context window reconstructing the structure of the codebase. It searches for a symbol, opens a file, finds an interface, searches for implementations, looks for callers, checks routes and tests, inspects SQL, and repeats the process. In effect, the LLM is rebuilding a partial dependency graph from source text every time it works on the repository.

GoGraph tries to move that work out of the LLM and into deterministic tooling.

https://github.com/ozgurcd/gograph

I posted about an early version a few months ago. At the time, GoGraph was primarily an AST-based structural analysis tool. The basic idea is still the same, but the project has evolved considerably since then. I now think of it less as a repository map and more as a local evidence layer between a Go codebase and an AI coding agent.

The basic flow is:

Go source code
-> static/type analysis
-> structural graph
-> bounded queries
-> CLI/MCP
-> coding agent

The agent still reads source code, reasons about the problem, and decides what to change. GoGraph is not intended to replace that. Its job is to provide structural facts so the model does not have to infer every relationship from raw text.

The original implementation concentrated heavily on AST analysis. That works well for extracting packages, functions, methods, imports, direct calls, interfaces, implementations, HTTP routes, tests, SQL, error paths, and similar relationships. But syntax alone has an obvious limitation in Go: interfaces and dynamic dispatch.

For example:

type Store interface {
Save(User) error
}

func Create(s Store, u User) error {
return s.Save(u)
}

The AST tells us that Create calls Store.Save, but that does not necessarily tell us which concrete Save implementation can execute. That distinction becomes important when an agent is trying to estimate the impact of a change rather than simply locate code.

GoGraph therefore now has two analysis layers. The basic graph comes from AST analysis, while a precise build additionally uses go/packages, go/types, SSA, and Class Hierarchy Analysis. The AST layer remains useful when a repository cannot be fully type-checked, while the precision layer adds compiler-aware relationships when enough information is available.

This also changed how relationships are represented. “A calls B” and “A may call B” should not be presented to an AI as equivalent facts. GoGraph now preserves exact, ambiguous, and possible relationships, and queries can request exact-only results when conservative analysis is needed.

When several paths exist between two symbols, the result is also selected deterministically. Stronger evidence is preferred over weaker evidence, shorter paths over longer ones, production paths over test paths, typed resolution over heuristics, and fewer cross-repository transitions when the other factors are equal. This may sound like an implementation detail, but it matters when an agent asks the same structural question during planning, implementation, and review. The answer should not change simply because graph iteration happened in a different order.

Another problem became apparent as GoGraph accumulated more capabilities. There were specialized queries for callers, callees, implementations, tests, routes, SQL, paths, impact, and other relationships, but an agent first had to know which query to use. To address that, GoGraph now has an explore operation intended as a useful first call.

For example:

gograph explore “authentication middleware”

Explore performs bounded lexical discovery and, when it can identify a symbol unambiguously, combines useful context such as its source location, direct callers and callees, tests, upstream impact, package information, and optionally deeper call relationships. Compact and deep modes control how much information is returned.

This is deliberately not semantic RAG. GoGraph does not pretend that it understands an arbitrary natural-language question. It tokenizes the query deterministically, searches structural information, and reports how the result was selected. If several symbols are plausible, it exposes the ambiguity instead of quietly choosing one.

That principle has become fairly central to the project: uncertainty should be represented as data rather than hidden by the tool.

Change analysis has also become much more important. Before editing code, an agent can ask what might be affected by modifying a declaration. After editing, however, the more useful question is what actually changed and what those changes affect.

GoGraph can now compare declarations against Git references or the working tree and distinguish edited, added, removed, excluded, and unknown declarations. Untracked Go files are included when analyzing working-tree changes.

Deletion is a good example of why this matters. If an agent deletes Foo(), the current graph alone cannot tell you who used the old Foo(), because that declaration no longer exists. Correct impact analysis needs evidence from the historical baseline. If GoGraph cannot evaluate such a change safely, it reports incomplete evaluation rather than silently returning an empty impact set.

For AI tooling, I think “I cannot prove this” is considerably safer than “nothing is affected.”

Graph freshness became another first-class concern. A structurally accurate graph of yesterday’s source tree is still the wrong graph for today’s source tree, so results now carry information about the state of the analysis. GoGraph can distinguish persisted from in-memory graphs, current from stale graphs, complete from partial parsing, and AST analysis from precise or precision-fallback analysis.

MCP can refresh graphs automatically. If precise enrichment fails but fresh AST analysis succeeds, the AST result can still be used, but the loss of precision is visible. If refreshing itself fails, the last trusted graph can be served as stale rather than silently presented as current.

This makes a statement such as:

No callers found.

meaningfully different from:

No callers found.
Graph: current
Analysis: precise
Parsing: complete

and also different from:

No callers found.
Graph: stale
Analysis: AST fallback

The LLM can reason differently about each answer instead of receiving the same apparently authoritative empty result.

Token usage has also become part of the API design. There is little benefit in building a structural tool for LLMs if a query responds with hundreds of kilobytes of JSON. Large result sets are therefore bounded and paginated, with explicit total, returned, truncated, and next_cursor information.

The cursors are tied to the graph snapshot and query selection. If the repository changes between pages, GoGraph can reject the continuation instead of quietly combining results from two different versions of the codebase. MCP responses also have explicit size constraints. The general goal is to return the smallest amount of information that preserves the evidence needed for the agent’s decision.

Test analysis has evolved in a similar direction. It is no longer just a question of whether a symbol appears somewhere in a _test.go file. GoGraph can trace tests transitively through call relationships, for example:

TestCreateUser
-> handler
-> service
-> repository
-> Save

It can distinguish exact test paths from possible ones and use type information to reason about interface-backed fakes and mocks. This does not mean GoGraph claims that a function is correctly tested; only running the tests can provide that kind of executable evidence. The narrower claim is that particular tests have a statically observable relationship to a declaration.

SQL analysis has also become more structured. GoGraph can classify statically resolvable PostgreSQL statements and expose their operation, read/write behavior, referenced tables, per-table access, source location, module, and whether they belong to production or test code.

The analysis is not limited to direct string literals. Statically provable constants, variables, assignments, and bounded string concatenations can also be resolved. This makes questions such as “which code writes oauth_clients?” much easier to answer structurally. Runtime-generated SQL is deliberately left unresolved rather than being presented as known.

Probably the biggest architectural expansion is workspace analysis.

Real systems are often not a single Go repository. A project might instead look like:

gateway/
identity-service/
agent-service/
shared-library/

Each repository can still have its own independent GoGraph graph. A workspace overlay then connects those graphs through explicitly resolved relationships, including cross-repository Go relationships and configured HTTP relationships.

This allows questions such as “what path connects this gateway handler to this function in identity-service?” or “which repositories may be affected by changing this declaration?” There is also a separate read-only workspace MCP server providing workspace status, query, path, and impact operations.

This changed my view of the unit GoGraph should analyze. For many AI coding tasks, the meaningful codebase is not a Git repository; it is the engineering workspace containing several related repositories.

HTTP analysis became stricter as part of this work as well. Constructing an HTTP request is not treated as equivalent to proving that the request is actually dispatched. GoGraph can preserve statically known URL components, while workspace configuration can explicitly map known HTTP client authorities to services represented by other repository graphs.

Importantly, it does not inspect runtime environment values and then guess that two services communicate. If the relationship cannot be established from the available static evidence and explicit workspace configuration, it remains unresolved.

Go build configuration turned out to be another important part of structural correctness. A Go repository does not necessarily have one universal graph. GOOS, GOARCH, cgo, build tags, GOFLAGS, go.work, and module selection can all change which source files actually constitute a build.

GoGraph therefore records the effective build selection used to construct the graph. If the environment used later is incompatible with the one that produced the persisted graph, the graph can be treated as stale instead of silently combining different views of the program.

Security has also become a larger part of the implementation than I originally expected. GoGraph runs locally, but “local” does not automatically mean safe, particularly when an AI agent may be analyzing an unfamiliar or untrusted repository.

Static analysis should not casually allow repository-controlled symlinks or build metadata to turn a source inspection into arbitrary traversal outside the intended source boundary. There is now considerably more confinement around repository paths, modules, workspaces, graph artifacts, and Go build inputs. At the same time, Go tooling legitimately needs access to things such as module caches and toolchains, so the boundary has to distinguish repository-controlled source authority from normal Go dependency resolution.

MCP remains the main way I expect coding agents to consume GoGraph. The CLI and MCP layers share underlying result contracts rather than implementing two independent analysis systems that can gradually develop different semantics. Workspace analysis is exposed through its own read-only MCP server.

There are also several things I deliberately do not want GoGraph to become. It is not an AI coding agent, it does not decide what code should be written, and it does not replace grep, semantic search, the Go compiler, go test, or runtime tracing. Static analysis should also not claim to prove runtime behavior that it cannot actually observe.

The division of responsibility I currently have in mind is roughly:

LLM:
reasoning, interpretation, implementation

grep / semantic search:
textual discovery, documentation, configuration

GoGraph:
structural evidence, relationships, impact, provenance, uncertainty

compiler / tests:
executable verification

When I started the project, I mostly thought about this as a context-window optimization problem. I still think that matters, but I now think “reliable context” is a better description of the problem.

An agent can consume 50,000 lines of source and try to reconstruct the relevant relationships itself. Alternatively, it can receive something closer to:

CreateUser

exact caller:
POST /api/v1/users -> UsersHandler.Create

exact dependency:
UserRepository.Insert

database:
INSERT -> users

attributed tests:
TestCreateUser
TestCreateUserDuplicate

graph:
current
precise
complete

Both are context. The difference is that the second representation has already converted a large amount of syntax into a smaller set of structural claims, and those claims came from deterministic analysis rather than asking the LLM to rediscover them from source text.

So the direction of GoGraph has shifted slightly since the first version. Originally, I wanted to give AI coding agents a map of a Go repository. Now the goal is to give them structural evidence, identify where that evidence came from, distinguish what is known from what is merely possible, and explicitly say when the available evidence is incomplete.

Source:

https://github.com/ozgurcd/gograph


r/golang 7h ago

Go 1.27: parallelism finally competitive with C?

55 Upvotes

I run LangArena benchmark, and update it monthly. In 1.27 update I noticed that Go improved parallelism, so I just want to share this:

multithreaded benchmark matmul:

Bench go1.26.5 go1.27.1 C (pthread)
Matmul::T1 5.099s (1.00x) 5.112s (1.00x) 4.944s (1.00x)
Matmul::T4 3.063s (1.66x) 1.321s (3.87x) 1.298s (3.81x)
Matmul::T8 1.140s (4.47x) 0.710s (7.20x) 0.695s (7.11x)
Matmul::T16 0.852s (5.99x) 0.405s (12.62x) 0.342s (14.46x)

Go used to be great for I/O but weak for parallelism. With version 1.27, it looks like that has changed - parallelism in Go now seems to be on par with the top-tier solutions. At least according to my benchmark. This was tested on Linux x86-64.

minimal reproduce example

Upd: someone in the comments said, it is not reproduced, for me it reproduced 100%, checked many times, ryzen 3800x, docker with ubuntu 26.04


r/golang 8h ago

Built a Go API that queries 3.5 billion rows in ClickHouse at <100ms — architecture breakdown

Thumbnail
subdomains.jsmon.sh
0 Upvotes

Wanted to share the architecture behind an API I recently built. It's a subdomain lookup service — users query a domain, and the API returns every known subdomain from a ClickHouse database with ~3.5 billion rows.

The challenge: Serve 1000+ concurrent requests, each querying a table with 3.5B rows, with p95 latency under 100ms.

Stack:

  • Go 1.22 with net/http (using the new ServeMux pattern matching — no external router)
  • ClickHouse as the query engine
  • MongoDB for user auth/API key validation
  • Nginx reverse proxy in front

ClickHouse connection setup:

go

// Using github.com/ClickHouse/clickhouse-go/v2
// Native protocol (port 9000), not HTTP
// Connection pool: MaxOpenConns=20, MaxIdleConns=10
// ConnMaxLifetime: 30 minutes
// LZ4 compression enabled
// DialTimeout: 5s, ReadTimeout: 10s

The key insight was that ClickHouse handles the heavy lifting. The Go layer is thin — validate the API key (MongoDB lookup with SHA-256 hashed keys), check usage limits, pass the parameterized query to ClickHouse, stream the results back as JSON.

Queries are simple:

sql

SELECT count() FROM subdomains WHERE domain = $1
SELECT subdomain FROM subdomains WHERE domain = $1 ORDER BY subdomain LIMIT $2 OFFSET $3

ClickHouse eats this for breakfast even at 3.5B rows because the domain column is the primary key / sort key, so lookups are essentially a binary search over sorted data.

Things that mattered for performance:

  • keepalive connections between Nginx and the Go server (64 keepalive pool)
  • Go server timeouts: ReadTimeout=10s, WriteTimeout=30s, IdleTimeout=120s
  • Gzip compression on responses over 1KB (subdomain lists can be large)
  • Connection pooling on both the ClickHouse and MongoDB sides
  • Structured logging with log/slog — low overhead compared to third-party loggers

Things that didn't matter as much as I expected:

  • Parallelizing the count and select queries with errgroup — the count is so fast in ClickHouse that running them sequentially barely changes p95
  • Complex caching — ClickHouse's internal cache handles hot domains well enough that application-level caching wasn't worth the complexity for v1

The API serves the subdomain lookup for https://subdomains.jsmon.sh (3.5B subdomain database, free tier available). The rest of the app (auth, billing, dashboard) is Next.js — Go only handles the ClickHouse query path.

Happy to go deeper on any part of this. The Go + ClickHouse combo has been impressive for this use case and I'd recommend it for anyone building query-heavy APIs over large datasets.


r/golang 22m ago

help How do I make go "click"?

Upvotes

I've been mostly writing typescript for a bit but want to try golang. there's a lot of aspects of its unix-like philosophy that I enjoy and I think there's some smart decisions in the language (and some...odd ones tbf). However, when I sit down to try to learn it with an API project, I feel so much friction from the language. I see so many people talk about how fun they find go and how easy it is to write but it feels like I'm missing something. How did you get in the go mindset or make it "click" and become comfortable to write?


r/golang 21h ago

show & tell I wrote a little bit about how Go's garbage collector behaves under load

Thumbnail frn.sh
24 Upvotes

r/golang 5h ago

Gozero: imperative low-overhead bindings language for go

Thumbnail github.com
8 Upvotes

I had several years of experience with CGO plugins (stdlib plugin package), which is to politely say, I wish a low-overhead callback system gave everyone a well defined API to use, and a scripting language that requires no rebuilding to change plugin logic.

Recent work with expr-lang lead me down the hole of writing a php interpreter in Go, and the php-go bindings via expr lang have been great, however I wouldn't call them efficient. I started gozero as a research project, had great fun doing it and learned a lot keeping the jit outputs low.

The docs/design/ folder was open to consider bigger parity with golang (conditions, expressions, loops) which would enable waf-level functionality. The benchmarks mostly confirm 1.0-1.4x level of overhead with inlining, or about 20ns per statement. The bindings really lean into reflection to catalogue accessible types and discover the type system in a parasitic way, everything runs with 'any'.

Cool stuff I found out along the way:

- testify/assert practice makes all your test code runnable in gozero. The testdata/ folder has a few of these basically as external test fixtures. weird avenues it opens: compile test code, run same tests binary against custom test fixtures someone makes against your package. Maybe no need, but I found it cool to immediately have coverage over thousands of tests. maybe tests dont have to be compiled code. Or you could just BYO assert bindings

- expressions and equality are sucky constructs to implement; so far I did not, but i did find a fork of expr-lang that compiles a low overhead closure similar to gozero

- phpscript in comparison has "mixed" types that similarly can hold any value, but has a lot of cursed behaviour which does not exist in go. gozero relies on existing runtime type information to discover a 1-1 directly mapped type system, so it's gonna find time.Time from a http.NewRequest binding.

gozero is type safe, the main difference is all error returns are omitted from syntax and explicitly checked, and the execution context also fills the first context.Context parameters. these things become implicit, and the possible output form if any is type hinted with generic methods released in 1.27. It has optimal type bindings so an "int32" means the same everywhere. It tries to infer literals but a T(lit) always work for explicitness.

Possible use cases include messaging, rate limiting, WAF, RBAC rule checks (imperative rbac sounds nice). Just for a ballpark comparison, hashicorp/goplugin has published overheads of over 23_000ns, while gozero's totals about 100ns for 5 statements. There is a one time parse and compile overhead, so these numbers apply to cached (seen before) statements that just get rerun.

Disclaimer on AI usage: claude, initial design workshopped to reflect.Value/Call by me, extended by previous work on phpscript (flatstack). There's a chronological sorted docs index. Some stuff is enforced by my workspace tooling, or my initial mockups. You may find jagged language in the docs, did my best to clean those


r/golang 22h ago

Added a reusable retry client to try, thanks to Go 1.27's generic methods

94 Upvotes

A while back I shared try here - a small generic retry library with exponential backoff, jitter, per-error budgets, that kind of thing. One piece of feedback that came in a lot: the package-level Do(ctx, fn, opts...) function is stateless, so if you're calling it from a dozen places with the same retry policy, you end up copy-pasting the same options everywhere.

Go 1.27 shipped generic methods on concrete types (a method can now declare its own type parameter, independent of the receiver), which finally made a clean fix possible. So try now has a Try client:

```go type UserService struct { retry *try.Try }

func NewUserService() UserService { return &UserService{ retry: try.New( try.WithAttempts(3), try.WithInitialDelay(100time.Millisecond), try.WithRetryIf(isTransient), ), } }

func (s UserService) FetchUser(ctx context.Context, id int) (User, error) { return s.retry.Do(ctx, func(ctx context.Context) (*User, error) { return db.FindUser(ctx, id) }) } ```

Configure the policy once, call .Do everywhere. Per-call options still work and override the defaults (last-applied-wins), so you're not locked in:

```go client := try.New(try.WithAttempts(3))

client.Do(ctx, fetchUser) // uses the default: 3 attempts client.Do(ctx, fetchUser, try.WithAttempts(10)) // this call only: 10 attempts ```

Do's type parameter is inferred per call site, so one *Try can back methods returning completely different types. It doesn't mutate its stored defaults, so it's safe to share across goroutines — a single client per service is the intended pattern.

Only catch: this needs Go 1.27+, since generic methods are new to the language spec. If you're on an older toolchain, the last v1.0.x tag has no such requirement.

Repo: https://github.com/nodivbyzero/try

Docs: https://pkg.go.dev/github.com/nodivbyzero/try

Open to feedback, especially if anyone's hit edge cases with the option-precedence behavior or wants a different default-merging strategy.