惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyberwarzone
Cyberwarzone
Vercel News
Vercel News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
aimingoo的专栏
aimingoo的专栏
B
Blog RSS Feed
A
About on SuperTechFans
T
The Blog of Author Tim Ferriss
爱范儿
爱范儿
腾讯CDC
S
SegmentFault 最新的问题
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
The Hacker News
The Hacker News
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
B
Blog
IT之家
IT之家
Spread Privacy
Spread Privacy
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
Cisco Blogs
Recent Announcements
Recent Announcements
H
Hacker News: Front Page
AI
AI
I
InfoQ
H
Heimdal Security Blog
T
Threatpost
Cisco Talos Blog
Cisco Talos Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
Intezer
W
WeLiveSecurity
SecWiki News
SecWiki News
MongoDB | Blog
MongoDB | Blog
宝玉的分享
宝玉的分享
博客园 - 【当耐特】
云风的 BLOG
云风的 BLOG
T
Threat Research - Cisco Blogs
V2EX - 技术
V2EX - 技术
N
News and Events Feed by Topic
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
O
OpenAI News
阮一峰的网络日志
阮一峰的网络日志
T
Troy Hunt's Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
雷峰网
雷峰网
T
Tor Project blog
有赞技术团队
有赞技术团队
Schneier on Security
Schneier on Security
Last Week in AI
Last Week in AI

Hacker News

Introducing Claude Opus 4.7 Qwen Studio The Future of Everything is Lies, I Guess: Where Do We Go From Here? GitHub - SeanFDZ/macmind: Single-layer transformer in HyperTalk for the classic Macintosh Show HN: Agent-cache – Multi-tier LLM/tool/session caching for Valkey and Redis Bonsai 1-bit WebGPU - a Hugging Face Space by webml-community Moving a large-scale metrics pipeline from StatsD to OpenTelemetry / Prometheus GitHub - Nightmare-Eclipse/RedSun: The Red Sun vulnerability repository GitHub - SethPyle376/hiraeth: Local AWS emulator focused on fast integration testing, with SQS support, SQLite-backed state, and a debug-friendly web UI. GitHub - macOS26/Agent: Any AI, replaces Claude Code, Cursor, OpenClaw. Over 18 LLM providers (Claude, OpenAI, Gemini, Ollama, Zai, HF, Qwen) wired into a native Mac app that writes code, builds Xcode projects, bumps versions, manages git, automates Safari, use AppleScript, JS or Accessibility, extend Agent! w/ MCP Servers, run tasks from your iPhone via Messages. YouTube now lets you turn off Shorts I Made a Terminal Pager Burgers | マクドナルド公式 Commands — HackerNews CLI documentation ChatGPT for Excel PiCore - Raspberry Pi Port of Tiny Core Linux Live Nation illegally monopolized ticketing market, jury finds Google Broke Its Promise to Me. Now ICE Has My Data. Founding Engineer at Adaptional | Y Combinator CRISPR takes important step toward silencing Down syndrome’s extra chromosome GitHub - saffron-health/libretto: The AI toolkit for building reliable browser automations US v. Heppner (S.D.N.Y. 2026) no attorney-client privilege for AI chats [pdf] Retrofitting JIT Compilers into C Interpreters IPv6 – Google The Accursèd Alphabetical Clock Cybersecurity Looks Like Proof of Work Now Fragments: April 14 Cal.com Goes Closed Source: Why AI Security Is Forcing Our Decision | Cal.com - Scheduling Software for Online Bookings Laravel raised money and now injects ads directly into your agent When moving fast, talking is the first thing to break Too much Discussion of the XOR swap trick – Heather Cafe Introduction to Spherical Harmonics for Graphics Programmers The Grand Line Building a Z-Machine in the worst possible language High-Level Rust: Getting 80% of the Benefits with 20% of the Pain GitHub - duguyue100/midnight-captain: Inspired by Midnight Commander, tailored to my taste. How to build a `git diff` driver · Jamie Tanna | Software Engineer Center for Responsible, Decentralized Intelligence at Berkeley The Local Universe’s Expansion Rate Is Clearer Than Ever, but Still Doesn’t Add Up - A new synthesis of astronomical measurements confirms a persistent mismatch that could point to physics beyond current models The air throughout our homes is infused with microplastics. But there are things you can do to breathe less of them The disturbing white paper Red Hat is trying to erase from the internet – OSnews The Future of Everything is Lies, I Guess: Annoyances ‘Abhorrent’: the inside story of the Polymarket gamblers betting millions on war Productive procrastination — Max van IJsselmuiden maps, territory and LMs 447 Terabytes per Square Centimetre at Zero Retention Energy: Non-Volatile Memory at the Atomic Scale on Fluorographane Show HN: Pardonned.com – A searchable database of US Pardons 20 Years on AWS and Never Not My Job The Seasons are Wrong Artemis II crew splashes down near San Diego after historic moon mission We gave an AI a 3 year retail lease in SF and asked it to make a profit | Andon Labs How a dancer with ALS used brainwaves to perform live On filing the corners off my MacBooks Installing every* Firefox extension OpenClaw’s memory is unreliable, and you don’t know when it will break Steve Blank Nowhere Is Safe Chimpanzees in Uganda locked in vicious 'civil war', say researchers watgo - a WebAssembly Toolkit for Go linux/Documentation/process/coding-assistants.rst at master · torvalds/linux GitHub - callumlocke/json-formatter: Makes JSON easy to read. Founding Product Engineer at Bild AI | Y Combinator A compelling title that is cryptic enough to get you to take action on it GitHub - Keychron/Keychron-Keyboards-Hardware-Design: Industrial design files for Keychron keyboards and mice. 100+ models with CAD assets in STEP, DXF, DWG, and PDF. Source-available, with commercial use allowed for original compatible accessories within the license terms. [ANNOUNCE] WireGuardNT v0.11 and WireGuard for Windows v0.6 Released 1D-Chess Helium Is Hard to Replace Cooperative Vectors Introduction | Evolve Keeping a Postgres queue healthy — PlanetScale Our response to the Axios developer tool compromise Do Americans read print books, e-books or audiobooks more? The Zettelkasten Method in Obsidian: A Practical Setup Guide Artemis II Is Competency Porn and We Are Starving For It WeakC4 Flight Viz — Cockpit View A Mexican surveillance giant you’ve never heard of is now watching the U.S. border Surelock: Deadlock-Free Mutexes for Rust RISC-V 101 – what is it and what does it mean for Canonical? | Ubuntu The Problem That Built an Industry How Much Linear Memory Access Is Enough? | Solidean Investigating Split Locks on x86-64 Simplest hash functions Sybilproof reputation mechanisms (2005) [pdf] What is a property? How Complex is my Code? Static code analysis in Kotlin — tools overview Toffoli gates are all you need PGLite evangelism dcmake: a new CMake debugger UI Clojure on Fennel part one: Persistent Data Structures Fragments: April 2 Python Release Python install manager 26.1 The Life and Death of the Book Review - Liberties Introducing Database Traffic Control — PlanetScale Bitcoin miners are losing $19,000 on every BTC produced as difficulty drops 7.8% God sleeps in the minerals Building slogbox Apple Silicon and Virtual Machines: Beating the 2 VM Limit Who was “Not Even Wrong” first? Pokemon Evolution Vs Darwinian Evolution The APL Programming Language Source Code
How to Implement an FPS Counter
2026-04-22 · via Hacker News

Posted: 2025-08-17

Updated: 2025-09-17

TL;DR: Don’t base your FPS calculations on a specific number of frames. Instead, maintain a rolling window of frames that happened during the last second. Use precise timers. Read on for more details.

I mentioned this in my post about the GMTK Game Jam 2025, but I wanted to cover it in detail.

Let’s say you want to display an FPS counter in your game, which you’ve seen many games do. First, let’s ask ourselves what it is that we want this piece of data to show us. We want to know how the game performs and whether it is, within most recent history, hitting the target of 30 or 60 FPS.

Sure, but then why don’t we just measure the amount of time it takes to process each frame? For 60 FPS, each frame needs to be prepared and rendered in less than 16.67 milliseconds. If we are consistently below that, we’re good. Well, FPS is a metric commonly shown to players and it’s widely accepted in the industry as a proxy for performance.

So, what we want to know is how quickly the game is able to produce new frames, but we’re going to display it as this proxy measure. And that is fine, so how do we do it?

If you search online for how to implement an FPS counter, you’ll probably run into one of the following methods, which I don’t think are the right approach.

Wrong ways

Method 1: FPS based on the latest frame

The pseudo-code would look like this:

    float fps = 0;
    Time prev = Time::current();

    while (true) {
        // handle input

        // update game state

        // render game and show FPS in UI

        Time curr = Time::current();

        fps = Duration::seconds(1) / (curr - prev);

        prev = curr;
    }

(You may disagree with me putting the calculation after the render call; feel free to put it before if you wish. I prefer it this way since we’re cutting each measurement along the borders of the loop.)

If we go back to thinking in terms of frame processing times, what is this telling us? It tells us the time it took to produce the latest frame. And that may be a useful thing to know. But if we care about the time taken for each individual frame, why not log all of them in a file for future analysis? If a single frame is really fast or really slow, the FPS counter will indicate that for a single frame, than come back to a normal value, and we most likely won’t notice.

FPS, by its name, is an aggregate measure, so we should be aggregating across multiple frames.

Method 2: FPS based on N latest frames

This method tracks the processing times for several most recent frames (eg. 5 or 10 of them) and displays an FPS based on the rolling average. One way to pseudo-code this would be:

    const int windowFrames = ...;

    float fps = 0;
    Queue<Duration> processingTimes;
    Time prev = Time::current();

    while (true) {
        // handle input

        // update game state

        // render game and show FPS in UI

        Time curr = Time::current();

        if (processingTimes.size() == windowFrames) processingTimes.pop();
        processingTimes.push(curr - prev);

        fps = Duration::seconds(1) / averageVal(processingTimes);

        prev = curr;
    }

We want to measure FPS based on the most recent performance history. What is the time length of this history? It depends on how quickly the frames are produced. So, the history size along which we measure depends on the values that are being measured!

Imagine what an FPS graph would look like, the x-axis being time and the y-axis being FPS. Well, it would be one misleading graph, as each value on the y-axis would have a dependency on itself since its own value extends how far back along the x-axis it looks.

Here’s an example of it where 3 frames were slower than usual (red) and 3 were faster than usual (green), with a window of 5 frames:

Notice how much smoother the graph is when frames are slow compared to when they are processed quickly. This graph is inconsistent along the time period it’s displaying. That’s why history needs to have a fixed duration.

OK way

Method 3: FPS based on one second, resetting each second

Another way to measure FPS you may find online is:

    float fps = 0;
    int frames = 0;
    Time prev = Time::current();

    while (true) {
        // handle input

        // update game state

        // render game and show FPS in UI

        Time curr = Time::current();

        if (prev + Duration::seconds(1) < curr) {
            fps = frames;
            frames = 0;

            while (prev + Duration::seconds(1) < curr) {
                prev += Duration::seconds(1);
            }
        }

        ++frames;
    }

(I’ve also seen examples where, in the if body, fps is smoothed out over consecutive values of frames.)

What does this show us? It shows us how many frames get rendered each second, but it’s only updated each second. And this is mostly correct for what it’s supposed to show.

On one hand, we may want to limit how often the FPS display in the UI changes to make it easier to read, since it wouldn’t be showing a different number each frame. On the other hand, you may find once per second updates to be too far apart.

Right ways

Now that we’ve seen a few implementations, let’s see how we can do it better. But first, let’s talk about real-time monitoring.

If you’re coming from the webdev world, this should be familiar to you. Let’s say you have a service or an application that does something and you want to monitor what is happening to it. A very common example is measuring the number of user HTTP requests. Now, you could create a graph that shows the number of requests currently being processed, but that is a value that would jump up and down wildly and the graph would be hard to read. Or, if the service is not constantly receiving requests, the values would a lot of the time be flat zeroes.

Instead, you want to smooth the fluctuations out by looking at a window of time instead of a single point. Each point on the graph would be the value of a function (such as average, or count, or max) applied to all recorded events within the time window ending at that point’s timestamp. In the example of an HTTP requests graph, the x-axis would be time and the y-axis would be the number of requests; each point’s y-axis value would be the count of recorded events (an event being an HTTP request reaching the server) during the last time window right-aligned to the x-axis value of the point.

Here’s an example of such a graph. Gray dots indicate time points where a request happened.

When a request is received, the graph goes up and stays up until “window” amount of time has passed since that request.

Choosing the length of the window is a trade-off: shorter windows are better at tracking rapid changes, while longer windows are good at showing long-term trends.

Coming back to our case of implementing an FPS counter, an “event” is the completion of a single frame. For the time window, it makes a lot of sense to make it 1 second long, though that’s not mandatory. Yes, FPS is the number of frames per second, but you can divide the value you read by the window size. Eg. if the window is 2 seconds, simply divide the number of frames in the window by 2 to get the FPS. These two time durations are separate properties of the counter.

Method 4: FPS based on frames within a rolling window

With all of that out of the way, the code is actually simple:

    const Duration window = ...;

    float fps = 0;
    Queue<Time> frameTimestamps;

    while (true) {
        // handle input

        // update game state

        // render game and show FPS in UI

        Time curr = Time::current();

        frameTimestamps.push(curr);
        while (frameTimestamps.next() + window < curr) frameTimestamps.pop();

        fps = frameTimestamps.size() * Duration::seconds(1) / window;
    }

A queue stores timestamps of the most recent frame completions. On each update, all frames that are further in the past than a single window are discarded.

If you wanted to, you could introduce another Time variable and use it to limit how often the FPS display updates to make it easier to read. The frequency at which you update the display does not need to correspond to rolling window’s length.

The way it’s implemented here means that, during the first window, reported FPS will be small and will gradually pick up as the queue gathers timestamps. You can fix this if you want, but personally, I don’t mind if just the first second is a bit off.

Method 5: FPS based on processing times of frames within a rolling window

The previous method is fine, but there is a small tweak we could do:

struct FrameEvent {
    Time timestamp;
    Duration processingTime;
};

// ...

    const Duration window = ...;

    float fps = 0;
    Queue<FrameEvent> frameEvents;
    Time prev = Time::current();

    while (true) {
        // handle input

        // update game state

        // render game and show FPS in UI

        Time curr = Time::current();

        frameEvents.push(FrameEvent{curr, curr - prev});
        while (frameEvents.next().timestamp + window < curr) frameEvents.pop();

        fps = Duration::seconds(1) / averageProcessingTime(frameEvents);

        prev = curr;
    }

Here, we track the processing times for each frame in the rolling window. For FPS, we first calculate the average processing time, then calculate the FPS value from that.

This is nice because you could use it internally to display average frame processing times instead of FPS. And it can easily be extended to track the slowest frame in the window, or the standard deviation in processing times, etc.

Final caveats

It is important to use a timer with sufficient precision. If you are using SDL, I suggest using SDL_GetPerformanceCounter() and SDL_GetPerformanceFrequency(). If you are not using SDL, but are using C++, std::chrono::high_resolution_clock should be a good option.

If you don’t have access to a queue implementation or don’t want memory allocators sporadically triggering, you can implement a circular buffer with limited capacity. Be aware of the edge case when the buffer fills up - you could just give it a high enough capacity that you don’t expect to reach in your game. If it does fill up, remove the oldest frame and add the latest one. The implementation in method 5 will calculate the FPS based on processing times of frames in the buffer. The time window may be shorter than configured, but it is still correct as an FPS estimate.

I hope this clears up how to track your game’s FPS and may your frame rates stay high!


Bonus method: FPS based on one second, resetting twice per second

Some time after posting this, I thought of a way to improve on method 3 by having the FPS display update multiple times per second (eg. twice per second).

    Uint64 horizon = Time::current();
    size_t cntPrev0 = 0, cntPrev1 = 0, cntNext = 0;

    while (true) {
        // handle input

        // update game state

        // render game and show FPS in UI

        Time curr = Time::current();

        while (horizon + Duration::seconds(0.5) < curr) {
            horizon += Duration::seconds(0.5);

            cntPrev0 = cntPrev1;
            cntPrev1 = cntNext;
            cntNext = 0;
        }

        ++cntNext;

        // FPS to display is cntPrev0 + cntPrev1
    }

The idea is to track the number of frames, but split in three counters accounting for half a second each. What we display in the UI is the number of frames that happened within the first two (“older”) counters.

As new frames are rendered, we increment the third (ie. “newest”) counter. Every half seconds, we shift the counters to the “left” and reset the third counter.

Here’s an ASCII illustration of one point in time:

     cntPrev0  cntPrev1  cntNext
         |         |         |
         V         V         V
    [  0.5s  ][  0.5s  ][  0.5s  ]
------------------------|------>
                        ^      ^
                        |      |
                     horizon   |
                            present

When the “present” moves forward further than half a second from the horizon, we shift to the “left”:

               cntPrev0  cntPrev1  cntNext
                   |         |         |
                   V         V         V
              [  0.5s  ][  0.5s  ][  0.5s  ]
----------------------------------|-->
                                  ^  ^
                                  |  |
                             horizon |
                                  present

This method is good when this is exactly what you need and don’t plan to change it much. It consumes less memory than methods using a queue.


https://www.growingwiththeweb.com/2017/12/fast-simple-js-fps-counter.html

https://stackoverflow.com/questions/28530798/how-to-make-a-basic-fps-counter

https://milvus.io/ai-quick-reference/what-is-a-rolling-window-in-time-series-analysis

https://cloud.google.com/monitoring/alerts/concepts-indepth#duration

https://docs.preset.io/docs/rolling-functions

https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.rolling.html

https://wiki.libsdl.org/SDL3/SDL_GetPerformanceCounter

https://wiki.libsdl.org/SDL3/SDL_GetPerformanceFrequency

https://en.cppreference.com/w/cpp/chrono/high_resolution_clock.html