Industry Insights 11 min read

Which Load Testing Tool Wins at 100k QPS? K6 vs Gatling vs FunTester Benchmarks

In a series of local benchmarks on a 2.6 GHz six‑core Intel i7 machine, the author compares K6, Gatling, and FunTester under 10 k to 20 k QPS loads, detailing CPU, memory, and response‑time metrics, analyzing script languages, JVM settings, and offering optimization suggestions for FunTester.

FunTester
FunTester
FunTester
Which Load Testing Tool Wins at 100k QPS? K6 vs Gatling vs FunTester Benchmarks

Background and Goal

The author previously compared several performance‑testing frameworks and now conducts a focused benchmark of three tools—K6, Gatling, and FunTester—using a local moco server. The aim is to evaluate their behavior at the 100 k QPS level on a single machine.

Test Environment

Hardware: 2.6 GHz six‑core Intel Core i7. CPU usage was monitored via Activity Monitor, assuming 100% represents one CPU thread (theoretical maximum 1200%). Memory usage was also tracked with Activity Monitor. All frameworks run on the JVM; default JVM parameters were used because the author was not familiar with tuning Scala or JVM settings.

Script Preparation

K6 : The script is the same as in the author’s earlier article (link omitted for brevity).

FunTester : Uses Java SDK and Groovy SDK Groovy Version: 3.0.8 JVM. Java heap size is set to 1 GB; other parameters remain default.

Gatling : Adapted from the built‑in template. The script (shown below) defines a simple HTTP scenario that repeatedly requests /m on the local server.

package computerdatabase

import scala.concurrent.duration._
import io.gatling.core.Predef._
import io.gatling.http.Predef._

class FunTester extends Simulation {
  val httpProtocol = http.baseUrl("http://localhost:12345/m")
  val scn = scenario("FunTester").repeat(120000){
    exec(http("FunTester").get("/m"))
  }
  setUp(scn.inject(atOnceUsers(10)).protocols(httpProtocol))
}

Benchmark Execution

Because the local network bandwidth was not a limiting factor, the tests were run with low thread counts to avoid saturating the CPU. The author explains the terminology: "concurrency" is used uniformly across all frameworks.

1 Concurrent User

Results:

K6 – CPU 136.75 %, Memory 97 MB, QPS 10 543, RT 1 ms

Gatling – CPU 88.01 %, Memory 344 MB, QPS 19 506, RT 1 ms

FunTester – CPU 56.12 %, Memory 539 MB, QPS 18 859, RT 1 ms

Gatling reported higher CPU consumption when generating the test report, which surprised the author. K6’s QPS appeared lower than expected, while FunTester used more memory but remained acceptable.

5 Concurrent Users

K6 – CPU 449.15 %, Memory 139.5 MB, QPS 37 219, RT 1 ms

Gatling – CPU 341.19 %, Memory 350.5 MB, QPS 63 624, RT 1 ms

FunTester – CPU 243.19 %, Memory 945 MB, QPS 71 930, RT 1 ms

Gatling’s report generation again consumed CPU comparable to a single thread, but the overall execution time grew noticeably.

10 Concurrent Users

K6 – CPU 702.05 %, Memory 299.9 MB, QPS 61 087, RT 1 ms

Gatling – CPU 524.70 %, Memory 350.2 MB, QPS 94 542, RT 1 ms

FunTester – CPU 460.13 %, Memory 1 170 MB, QPS 91 360, RT 1 ms

K6’s CPU usage approached its limit, resulting in lower QPS. Gatling’s report generation took a long time (3 million records). FunTester’s memory consumption exceeded 1 GB.

20 Concurrent Users

K6 – CPU 718.74 %, Memory 370 MB, QPS 75 980, RT 1 ms

Gatling – CPU 585.97 %, Memory 350 MB, QPS 113 355, RT 1 ms

FunTester – CPU 528.03 %, Memory 1 770 MB, QPS 104 375, RT 1 ms

K6’s performance degraded further due to CPU bottleneck; Gatling maintained the highest QPS but still required significant CPU for report generation; FunTester continued to consume the most memory.

Observations and Analysis

The author notes three main reasons why FunTester consumes more resources:

It performs additional object marking.

It executes more conditional checks during termination.

It stores test data synchronously in memory.

Gatling appears to create more threads (possibly asynchronous handling) and does not retain certain compatibility features that FunTester keeps.

Optimization Suggestions for FunTester

Convert non‑essential processing to asynchronous execution.

Replace the current in‑memory metadata storage with a lighter alternative.

Gradually remove legacy compatibility code (already partially done).

Core Execution Code (FunTester)

@Override
public void run() {
    try {
        before();
        long ss = Time.getTimeStamp();
        int times = 0;
        long et = ss;
        while (true) {
            try {
                executeNum++;
                long s = Time.getTimeStamp();
                doing();
                et = Time.getTimeStamp();
                int diff = (int) (et - s);
                costs.add(diff);
            } catch (Exception e) {
                logger.warn("Execution task failed!", e);
                errorNum++;
            } finally {
                if ((isTimesMode ? executeNum >= limit : (et - ss) >= limit) || ThreadBase.needAbort() || status())
                    break;
            }
            long ee = Time.getTimeStamp();
            if ((ee - ss) / 1000 > RUNUP_TIME + 3)
                logger.info("Thread:{}, executions:{}, errors:{}, total time:{} s", threadName, executeNum, errorNum, (ee - ss) / 1000.0);
            Concurrent.allTimes.addAll(costs);
            Concurrent.requestMark.addAll(marks);
        }
    } catch (Exception e) {
        logger.warn("Execution task failed!", e);
    } finally {
        after();
    }
}

Overall, Gatling achieved the highest QPS, K6 showed lower throughput due to CPU limits, and FunTester consumed the most memory but remained functional. The author plans to address FunTester’s memory usage and synchronous processing in future releases.

JVMLoad TestingGatlingperformance benchmarkingFunTesterK6
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.