Skip to content

PandaCore · built in-house

Faster. Lighter. More efficient.Runs on every device.

PandaCore is the core engine PandaFan builds for its clients. Using the open-source tun-bench tool, we put it beside two open-source engines known for industry-leading performance on one Mac to measure throughput, CPU and memory.

These comparisons are here to help you understand the engine underneath PandaFan; a loopback test only shows the performance ceiling and says little about everyday use. We respect the open-source community and sponsor open-source projects from time to time. Our thanks to everyone who contributes.

The same 10 seconds, at measured rates.

TUN mode · Single-stream TCP download · Three-run medians

0.0×

Single-stream TCP upload

vs Open-source engine 2 · 32.9 vs 2.6 Gbit/s

1.2× · vs Open-source engine 1

0.0×

Single-stream TCP download

vs Open-source engine 2 · 29.2 vs 3.0 Gbit/s

1.1× · vs Open-source engine 1

0%

Memory with 1000 connections

vs Open-source engine 2 · 20 vs 95 MiB

−14% · vs Open-source engine 1

0%

CPU per Gbit on a single-stream download

vs Open-source engine 2 · 3.27 vs 39.64 %/Gbit

−7% · vs Open-source engine 1

Throughput

Faster uploads. Faster downloads.

Three rounds on the same M1 Mac mini, with engine order reversed in round two. Upload and download each cover one stream, eight streams, and a 1 Gbit/s cap: five seconds per cell after one second of warmup. Every metric uses its three-run median.

Mac mini · Apple M1 · macOS 26.5.1 · MTU 4064

Upload · single stream

12.7×vs Open-source engine 2

1.2×vs Open-source engine 1

32.927.32.6Gbit/s

Download · single stream

9.7×vs Open-source engine 2

1.1×vs Open-source engine 1

29.227.33.0Gbit/s

Upload · 8 streams

10.4×vs Open-source engine 2

Tievs Open-source engine 1

20.219.41.9Gbit/s

Download · 8 streams

3.0×vs Open-source engine 2

Tievs Open-source engine 1

18.117.46.1Gbit/s

Full matrix against two engines: 38 wins · 8 ties · 0 losses

Differences below 5% count as ties. The tally covers six throughput, six CPU/Gbit, six energy/Gbit and five memory measurements against each engine. A median lead is not a win in every round.

Complete comparison data23 cells

23 metrics against two engines: 46 comparisons. Every value is a three-run median; CPU/Gbit and energy/Gbit are computed per round before aggregation. The download includes the generated data and all original rounds.

CellPandaCoreOpen-source engine 1Open-source engine 2Comparison
Higher is betterUpload · single streamGbit/s32.8727.292.5812.7× · vs Open-source engine 2+1174% · PandaCore leads1.2× · vs Open-source engine 1+20% · PandaCore leads
Upload · 8 streamsGbit/s20.1819.431.9410.4× · vs Open-source engine 2+938% · PandaCore leads1.0× · vs Open-source engine 1+4% · Tie
Upload · 1 Gbit/sGbit/s0.990.991.001.0× · vs Open-source engine 10% · Tie1.0× · vs Open-source engine 20% · Tie
Download · single streamGbit/s29.1827.303.019.7× · vs Open-source engine 2+870% · PandaCore leads1.1× · vs Open-source engine 1+7% · PandaCore leads
Download · 8 streamsGbit/s18.1217.446.083.0× · vs Open-source engine 2+198% · PandaCore leads1.0× · vs Open-source engine 1+4% · Tie
Download · 1 Gbit/sGbit/s0.990.991.001.0× · vs Open-source engine 10% · Tie1.0× · vs Open-source engine 20% · Tie
Lower is betterCPU · Upload · single stream%/Gbit2.973.5598.2433.0× · vs Open-source engine 2−97% · PandaCore leads1.2× · vs Open-source engine 1−16% · PandaCore leads
CPU · Upload · 8 streams%/Gbit4.754.91170.1135.8× · vs Open-source engine 2−97% · PandaCore leads1.0× · vs Open-source engine 1−3% · Tie
CPU · Upload · 1 Gbit/s%/Gbit24.3828.11150.126.2× · vs Open-source engine 2−84% · PandaCore leads1.2× · vs Open-source engine 1−13% · PandaCore leads
CPU · Download · single stream%/Gbit3.273.5239.6412.1× · vs Open-source engine 2−92% · PandaCore leads1.1× · vs Open-source engine 1−7% · PandaCore leads
CPU · Download · 8 streams%/Gbit5.455.6764.3411.8× · vs Open-source engine 2−92% · PandaCore leads1.0× · vs Open-source engine 1−4% · Tie
CPU · Download · 1 Gbit/s%/Gbit27.8131.2477.732.8× · vs Open-source engine 2−64% · PandaCore leads1.1× · vs Open-source engine 1−11% · PandaCore leads
Lower is betterEnergy · Upload · single streamJ/Gbit0.089020.113321.2940914.5× · vs Open-source engine 2−93% · PandaCore leads1.3× · vs Open-source engine 1−21% · PandaCore leads
Energy · Upload · 8 streamsJ/Gbit0.114450.139701.1990910.5× · vs Open-source engine 2−90% · PandaCore leads1.2× · vs Open-source engine 1−18% · PandaCore leads
Energy · Upload · 1 Gbit/sJ/Gbit0.015500.017242.07201134× · vs Open-source engine 2−99% · PandaCore leads1.1× · vs Open-source engine 1−10% · PandaCore leads
Energy · Download · single streamJ/Gbit0.088230.101990.9494910.8× · vs Open-source engine 2−91% · PandaCore leads1.2× · vs Open-source engine 1−13% · PandaCore leads
Energy · Download · 8 streamsJ/Gbit0.099070.111840.735357.4× · vs Open-source engine 2−87% · PandaCore leads1.1× · vs Open-source engine 1−11% · PandaCore leads
Energy · Download · 1 Gbit/sJ/Gbit0.017660.018840.7850444.5× · vs Open-source engine 2−98% · PandaCore leads1.1× · vs Open-source engine 1−6% · PandaCore leads
Lower is betterMemory · idleMiB · physical_footprint9.4412.4213.801.5× · vs Open-source engine 2−32% · PandaCore leads1.3× · vs Open-source engine 1−24% · PandaCore leads
Memory · 250 connectionsMiB · physical_footprint12.5016.8135.812.9× · vs Open-source engine 2−65% · PandaCore leads1.3× · vs Open-source engine 1−26% · PandaCore leads
Memory · 500 connectionsMiB · physical_footprint15.1118.9255.003.6× · vs Open-source engine 2−73% · PandaCore leads1.3× · vs Open-source engine 1−20% · PandaCore leads
Memory · 750 connectionsMiB · physical_footprint17.6420.4773.954.2× · vs Open-source engine 2−76% · PandaCore leads1.2× · vs Open-source engine 1−14% · PandaCore leads
Memory · 1000 connectionsMiB · physical_footprint20.2423.6695.114.7× · vs Open-source engine 2−79% · PandaCore leads1.2× · vs Open-source engine 1−14% · PandaCore leads

Memory, CPU & energy

Less memory, less CPU.

Memory is recorded from idle through 1,000 connections. CPU and energy are normalized by traffic volume across full-speed and 1 Gbit/s capped tests, making different rates comparable.

Memory

Memory from 0 to 1,000 connections

MiB · Lower is better

Idle memory −32% (vs Open-source engine 2); with 1000 connections −79% (vs Open-source engine 2).

CPU · Full speed & capped

CPU per Gbit/s

%/Gbit · Lower is better

CPU per Gbit/s at full speed with 8 streams: download −92% (vs Open-source engine 2), upload −97% (vs Open-source engine 2); PandaCore 5.5% on download against 64.3% for Open-source engine 2.

Process energy · Full speed

Estimated CPU energy per Gbit

J/Gbit · Lower is better

Single-stream process CPU energy/Gbit: download −91%, upload −93% vs Open-source engine 2. This does not measure whole-device power or battery life.

Process energy · 1 Gbit/s cap

Energy at the same rate

J/Gbit · Lower is better

All three engines reach about 0.99–1.00 Gbit/s. Process CPU energy is divided by actual traffic volume; lower is better. This is not whole-device power.

These are loopback tests: traffic enters the engine through its TUN and lands on a server on the same machine, so they show the engine’s ceiling. Results vary with hardware, OS version and background load; real-world speed also depends on the line, the protocol and the server.

The apps

A powerful engine. A simple app.

Speed is the foundation. Simplicity is the everyday.

Not connected

One tap. Connected.

Sign in, tap once, and you’re on. The best route is chosen for you.

Smart routing

Local sites go direct, overseas sites go through PandaFan. Rules are built in and kept current, and your own rules always apply.

Enhanced mode

One switch, and every app on your computer or phone goes through PandaFan. Nothing to configure, app by app.

Compatible · free

Your config stays. The engine is free.

PandaCore reads the same YAML config as mihomo. The engine itself is a free download and runs without a PandaFan account.

Three commands. Try it now.
# download (Linux x86_64)
curl -fL https://build.bamboe.app/panda-core/latest/pandacore-linux-amd64 -o pandacore && chmod +x pandacore
# check your existing config
./pandacore -f config.yaml -t
INFOpanda_config: Config loaded: mode=rule, proxies=6, rules=3
INFOpandacore: Configuration test passed
# start
./pandacore -f config.yaml
INFOpandacore: panda-core is running
INFOpanda_listener::mixed: Mixed listener 'mixed' on 127.0.0.1:7890
INFOpanda_api: REST API listening on 127.0.0.1:9090
  • Your config, as is

    Proxies, groups, rules, DNS and TUN are read exactly as mihomo writes them. Add -t to check before you start.

  • Same control API

    The external API matches mihomo, so your dashboards and tools connect as they are.

  • Free. No account required.

    Download and run your own config. Connecting a PandaFan account is a single login command.

  • A few protocols such as Shadowsocks, VMess, Snell and SSH are not supported and need to be removed from the config.

Method

How we test

All public metrics come from tun-bench: the same Mac, TCP/IPv4 direct TUN loopback and MTU. Three engines run sequentially in three rounds, with reversed order in round two and 30-second cooldowns between rounds. Each metric is the median of all three rounds.

tun-bench creates a TUN interface and uses iperf3 to send traffic through the engine to a local server. Upload and download cover one stream, eight streams, and one stream capped at 1 Gbit/s.

CPU uses cumulative process CPU time per traffic volume. macOS energy uses the process CPU-energy estimate from proc_pid_rusage. Memory records physical footprint, not RSS, at 0, 250, 500, 750 and 1,000 idle connections.

These results apply to this M1 Mac mini TUN loopback test, not every device, protocol or individual run. The measured binary reports 1.9.2; its optimizations shipped in 1.9.3. This is not a new measurement of the 1.9.3 release artifact. Real network speed also depends on routes, protocols and servers.

Measured
2026-09-19
Aggregation
3 runs per metric, aggregated by median
Machine
Mac mini · Apple M1 · macOS 26.5.1
Tool
tun-bench · 201b780
PandaCore
1.9.2 + macOS optimizations (shipped in 1.9.3)
Open-source engine 1
1.15.0-alpha.4
Open-source engine 2
1.19.31 · gvisor
MTU
4064
Throughput
5 s per cell after a 1 s warm-up, 8 workers
Memory
250 idle connections per step up to 1000, 64-byte payload each
Load
Process CPU time and estimated CPU energy, not whole-device power

Reproduce it

The same tun-bench configuration reruns this comparison on your own machine. tun-bench drives the two open-source engines natively; a small patch teaches it to drive PandaCore.

sudo ./tun-bench run tun-bench-darwin.yml
Configurationtun-bench-darwin.yml · 36 lines
# tun-bench matrix behind the macOS numbers on pandafan.app/pandacore (SagerNet/tun-bench, run as root).
# Point `version` at your PandaCore binary: an absolute path is used as a local build, while tun-bench
# downloads the open-source engines itself. Run three rounds with reversed implementation order in
# round two and a 30-second cooldown between rounds; retain all rounds and aggregate by median.
environments:
  darwin:
    type: local
    os: darwin

implementations:
  pandacore:
    type: mihomo
    version: /usr/local/bin/pandacore
  sing-box:
    type: sing-box
    version: 1.15.0-alpha.4
  mihomo:
    type: mihomo
    version: 1.19.31

matrix:
  - name: typical
    implementation: [ pandacore, sing-box, mihomo ]
    ip: [ 4 ]
    network: [ tcp ]
    stack: [ pandacore/panda, sing-box/go, mihomo/gvisor ]
    mtu: [ 4064 ]
    include:
      - bitrate: 0
      - parallel: 8
      - bitrate: 1Gbps
  - name: memory
    type: memory
    implementation: [ pandacore, sing-box, mihomo ]
    stack: [ pandacore/panda, sing-box/go, mihomo/gvisor ]
    mtu: [ 4064 ]
    ip: [ 4 ]
    network: [ tcp ]
Patchtun-bench-pandacore.patch · 103 lines

The patch lets tun-bench treat a local path as the PandaCore binary, writes the TUN section in PandaCore’s configuration format, and moves the test subnet away from PandaCore’s default address.

diff --git a/artifacts.go b/artifacts.go
index 112f4e2..791ba40 100644
--- a/artifacts.go
+++ b/artifacts.go
@@ -137,7 +137,10 @@ func (p *artifactPreparer) binary(ctx context.Context, software, version, source
 		return binary, unsupported
 	}
 	var err error
-	if sourcePackage != "" {
+	if sourcePackage == "" && filepath.IsAbs(version) {
+		// A version given as an absolute path is a locally built executable: use it as is.
+		binary.path, binary.version = version, "local"
+	} else if sourcePackage != "" {
 		directory, createErr := os.MkdirTemp(p.directory, "package-*")
 		if createErr != nil {
 			return binary, createErr
diff --git a/benchmark.go b/benchmark.go
index ef4297c..b5ecaba 100644
--- a/benchmark.go
+++ b/benchmark.go
@@ -391,12 +391,28 @@ func (b *benchmark) startSubject(ctx context.Context, probePort int) error {
 		} else {
 			listener["inet4-address"] = []string{b.environment.address.String()}
 		}
-		content, err = yaml.Marshal(map[string]any{
+		document := map[string]any{
 			"mode": "rule", "log-level": "warning", "ipv6": true, "find-process-mode": "off",
 			"listeners": []any{listener},
 			"proxies":   []any{map[string]any{"name": "relay", "type": "socks5", "server": relayAddress, "port": relayPort, "udp": true}},
 			"rules":     []string{"MATCH,relay"}, "dns": map[string]any{"enable": false},
-		})
+		}
+		if strings.Contains(filepath.Base(b.options.executable), "pandacore") {
+			// pandacore takes mihomo's CLI and YAML but declares the TUN in the top-level `tun:` section.
+			delete(document, "listeners")
+			// pandacore forwards the benchmark target directly, like sing-box: the TUN rewrites the
+			// target address to loopback (destination-override) and the rule sends it to DIRECT, so no
+			// SOCKS5 hop is measured. Its preflight wants the host default route; outbound binding skips
+			// loopback destinations.
+			document["tun"] = map[string]any{
+				"enable": true, "device": b.environment.interfaceName, "stack": stack, "mtu": b.options.MTU,
+				"auto-route": false, "auto-detect-interface": true,
+				"destination-override": map[string]any{b.environment.target.String(): b.options.loopback()},
+			}
+			document["rules"] = []string{"IP-CIDR," + targetPrefix + ",DIRECT,no-resolve", "MATCH,DIRECT"}
+			document["log-level"] = "info" // keep the TUN driver lines visible when the interface never appears
+		}
+		content, err = yaml.Marshal(document)
 		path = filepath.Join(b.directory, "mihomo.yaml")
 		args = []string{"-f", path, "-d", b.directory}
 	case "v2ray", "xray":
diff --git a/configuration.go b/configuration.go
index d9107ef..00eba19 100644
--- a/configuration.go
+++ b/configuration.go
@@ -273,7 +273,7 @@ func readConfiguration(path, matrixName, measurementType string) (runConfigurati
 			}
 			implementation.Version = strings.TrimPrefix(implementation.Version, "v")
 			if implementation.Version != "" {
-				if implementation.Version != "latest" && !semver.IsValid("v"+implementation.Version) {
+				if implementation.Version != "latest" && !filepath.IsAbs(implementation.Version) && !semver.IsValid("v"+implementation.Version) {
 					return configuration, nil, E.New(name, ": invalid release version ", implementation.Version)
 				}
 			} else if implementation.Package != "" && !filepath.IsAbs(implementation.Package) {
diff --git a/network.go b/network.go
index 4aa15e2..ff3172f 100644
--- a/network.go
+++ b/network.go
@@ -18,8 +18,8 @@ import (
 var errUDPPayloadIntegrity = E.New("UDP payload integrity failure")
 
 func prepareNetwork(ctx context.Context, configuration benchmarkOptions, placement *environment) error {
-	placement.address = netip.MustParsePrefix("198.18.0.1/29")
-	placement.target = netip.MustParseAddr("198.18.0.3")
+	placement.address = netip.MustParsePrefix("198.19.0.1/29")
+	placement.target = netip.MustParseAddr("198.19.0.3")
 	if configuration.IP == 6 {
 		placement.address = netip.MustParsePrefix("fd00::1/125")
 		placement.target = netip.MustParseAddr("fd00::3")
@@ -30,7 +30,7 @@ func prepareNetwork(ctx context.Context, configuration benchmarkOptions, placeme
 	}
 	subnets := []netip.Prefix{placement.address.Masked()}
 	if configuration.software == "leaf" && configuration.IP == 6 {
-		subnets = append(subnets, netip.MustParsePrefix("198.18.0.0/29"))
+		subnets = append(subnets, netip.MustParsePrefix("198.19.0.0/29"))
 	}
 	for _, subnet := range subnets {
 		for _, prefix := range routes {
diff --git a/platform_linux.go b/platform_linux.go
index 227cc0d..3546b50 100644
--- a/platform_linux.go
+++ b/platform_linux.go
@@ -209,6 +209,10 @@ func prepareInterface(configuration benchmarkOptions, placement environment, net
 		return false, nil
 	}
 	queues, err := filepath.Glob(filepath.Join("/sys/class/net", placement.interfaceName, "queues/tx-*"))
+	if strings.Contains(filepath.Base(configuration.executable), "pandacore") {
+		// pandacore always opens its multi-queue TUN with its own lane count; accept whatever it created.
+		return len(queues) >= 1, err
+	}
 	return len(queues) == configuration.queues, err
 }
 

Six kinds of device. One engine.

PandaCore ships inside every official app: same behavior, updated together. Tap a platform to download.

  • One account for every device. Nothing to buy twice.
  • Apps update themselves, so every engine improvement arrives on its own.
  • Support around the clock, whenever you need it.

Try it now.

Sign up, download the app and log in. Phone, tablet and computer. One account.

View Pricing