Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ add_library(core STATIC
src/core/boot.cpp
src/core/retest.hpp
src/core/retest.cpp
src/core/busy_card.hpp
src/core/busy_card.cpp
src/core/task_xml.hpp
src/core/task_xml.cpp
src/core/watchdog.hpp
Expand Down Expand Up @@ -87,9 +89,10 @@ add_library(hw STATIC
src/hw/boot_task.hpp src/hw/boot_task.cpp
src/hw/update_io.hpp src/hw/update_io.cpp
src/hw/nvidia_app.hpp src/hw/nvidia_app.cpp
src/hw/gpu_users.hpp src/hw/gpu_users.cpp
)
target_include_directories(hw PUBLIC src)
target_link_libraries(hw PUBLIC core d3d11 dxgi d3dcompiler shell32 ole32 oleaut32 taskschd winhttp bcrypt version
target_link_libraries(hw PUBLIC core d3d11 dxgi d3dcompiler shell32 ole32 oleaut32 taskschd winhttp bcrypt version pdh
PRIVATE gao_warnings gao_hardening)

# UI-independent application logic shared by both executables.
Expand Down Expand Up @@ -145,6 +148,7 @@ add_executable(core_tests
tests/test_stability.cpp
tests/test_journal.cpp
tests/test_search.cpp
tests/test_busy_card.cpp
tests/test_boot.cpp
tests/test_retest.cpp
tests/test_task_xml.cpp
Expand All @@ -165,6 +169,7 @@ add_executable(app_tests
tests/test_driver_fault.cpp
tests/test_run_log.cpp
tests/test_nvidia_app.cpp
tests/test_gpu_users.cpp
)
target_link_libraries(app_tests PRIVATE app doctest gao_warnings)
add_test(NAME app_tests COMMAND app_tests)
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Both columns are five minutes of full load, so the card is heated through on bot

1. Download `GpuAutoOptimizer-<version>-win-x64.zip` from the [latest release](https://github.com/Rovey/gpu-auto-optimizer/releases/latest) and unzip it anywhere.
2. Start **`GpuAutoOptimizer.exe`**.
3. Open **Optimize** and press **Optimize GPU**: **All in one** is selected, and you can pick another profile first. Close games and other programs that use the graphics card before you do, and the tools that tune or draw on it: close MSI Afterburner and RivaTuner, switch **Automatic Tuning** off in the NVIDIA app (System, Performance), and keep the NVIDIA overlay (Alt+Z) closed during the run. The app asks for administrator rights, because it changes clocks and power limits.
3. Open **Optimize** and press **Optimize GPU**: **All in one** is selected, and you can pick another profile first. Close games and other programs that use the graphics card before you do, and the tools that tune or draw on it: close MSI Afterburner and RivaTuner, switch **Automatic Tuning** off in the NVIDIA app (System, Performance), and keep the NVIDIA overlay (Alt+Z) closed during the run. The app asks for administrator rights, because it changes clocks and power limits. The run looks for itself first: when other programs are using the card it names them (an editor or a browser that stands open is enough to cost several percent) and asks whether to check again, go on anyway or cancel. It closes nothing for you.
4. That is all. When the run is done the result is applied, saved, and kept at every logon. Untick **Keep the result applied at every logon** before the run if you only want to try it.

> [!NOTE]
Expand Down Expand Up @@ -206,6 +206,7 @@ flowchart LR
| `gao --curve` | Prints the voltage/frequency curve as the driver reports it; changes nothing |
| `gao --stress <seconds>` | Runs the stress test alone and prints its verdict; changes nothing |
| `gao --bandwidth [--after-load <sec>]` | Measures the current memory bandwidth and shows every run of the measurement; with `--after-load`, right after that many seconds of the stress load, as a run measures it |
| `gao --busy` | Shows the other programs that use the card right now, as a run looks for them before it starts; changes nothing |
| `gao --update` | Installs the latest release into the folder `gao.exe` is in, if there is a newer one |
| `gao --stress-recreate` | Runs the stress test, rebuilds its GPU device, runs it again; prints both scores and the rebuild time; changes nothing |

Expand All @@ -228,7 +229,7 @@ From 0.3.1 on, click **Update available** at the bottom left of the window. From
<details>
<summary><b>Does it work alongside MSI Afterburner or the NVIDIA app?</b></summary>

Not during a run: a run does not start while MSI Afterburner or RivaTuner Statistics Server (`MSIAfterburner.exe`, `RTSS.exe`) is running, and says which one to close. Close it completely, also its icon in the notification area, and switch off its start with Windows if it applies settings there. Outside a run they can coexist as long as only one of them applies settings: if Afterburner applies an offset while GPU Auto Optimizer keeps its tune applied, the watchdog notices, says so in a notification and leaves the settings alone.
Not during a run: a run does not start while MSI Afterburner or RivaTuner Statistics Server (`MSIAfterburner.exe`, `RTSS.exe`) is running, and says which one to close. Close it completely, also its icon in the notification area, and switch off its start with Windows if it applies settings there. Outside a run they can coexist as long as only one of them applies settings: if Afterburner applies an offset while GPU Auto Optimizer keeps its tune applied, the watchdog notices, says so in a notification and leaves the settings alone. Other programs that merely use the card (a browser, an editor, a video) are a different matter: they change nothing on it, so a run is not refused next to them. It names them and asks, because they make everything it measures lower and less steady. On the command line it stops with that list; add `--busy-ok` as the last argument to go on as it is.

The NVIDIA app can stay installed. Two of its features get in the way of a run: **Automatic Tuning** (System, Performance) applies an overclock of its own and scans again from time to time, so switch it off, for a run and for as long as you keep a tune of this app applied; and the **overlay** (Alt+Z) draws on programs that use the card, so leave it closed while a run is going. A run does not start while Automatic Tuning is on, and `gao --status` shows what the app reads (`nvidia app: Automatic Tuning off`). The overlay it cannot see: that one is yours to keep closed.
</details>
Expand Down
1 change: 1 addition & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ The applied offset is always at least one step below the confirmed edge. Profile
| Every line of a run is also in `run.log` | `logged()` wraps the log hook of both front ends: a line is appended and flushed before it is shown, so the step a machine freezes in is the last line on disk. One file in the state folder, moved aside at 1 MB (`run.log.old`). Only elevated and only after `ensure_app_dir` has verified the folder: an elevated process appends to nothing a user could have put there. |
| A run does not start while the NVIDIA app's Automatic Tuning is on | Switched on, a service of the driver scans the card when it sees fit: it loads it, changes its clocks, and starts again by itself (hardware check 89: a scan that was stopped started again 14 ms later, and after a restart of the service it resumed within two seconds). A run next to that measures nothing it can trust, so it is refused, like a run next to Afterburner, and with the same weight: a courtesy, not a guard. Read from where that service keeps the switch, `%ProgramData%\NVIDIA Corporation\nvtopps\nvtopps.db3`, table `ocScannerSettings`, fields `backgroundScanEnabled` and `grdOcEnabled`, joined to `TelemetryGpuCfg` so that only a card that is in the PC counts (a row of an earlier card can never be switched off again). That this is the source and not a copy of it was measured, not assumed: with the field set the service took the scan up again after every restart, with it cleared it did not, while the NVIDIA app itself showed the switch as off. Nothing documents the file. So: opened read-only and immutable (no lock, the service never waits for us), through `winsqlite3.dll` from System32 (no dependency added), and everything that is not exactly as expected reads as "unknown", which starts the run as before. The registry value the app sets next to it (`HKLM\SOFTWARE\NVIDIA Corporation\Global\NvApp\OC\<user SID>`) changes at the same moment but belongs to the app, not to the service, and is not used. **The overlay is not checked:** no setting for it was found, its processes run whether it is open or not, and the one value that moved (`IsShadowPlayEnabled`) followed the service being up, not a switch. NVAPI was not tried: an undocumented call is no better than an undocumented file, and it cannot be compared with anything. |
| A run does not start next to MSI Afterburner or RivaTuner | `other_tool_refusal`, asked before anything else in `run_optimize`, `run_undervolt`, `run_all_in_one` and `run_retest`, so the window and `gao` say the same. Afterburner sets clocks and fans itself; RivaTuner hooks into every program that uses the card, the stress load among them. By file name (`MSIAfterburner.exe`, `RTSS.exe`), which is all a list of processes gives; a list that cannot be read counts as "none runs", because the run's own read-backs and the watchdog are what it relies on. Not the cause of the freeze above: on that PC Afterburner was closed and RivaTuner not installed. |
| Before a run the tool looks at what else uses the card, names it, and asks | A run measures the card under full load, and every other program on the card takes from what it measures. That was found the long way: for a whole day of card tests the reference RTX 4070 scored about 5300 it/s in the stress load and read 444 to 455 GB/s of memory bandwidth, scattering by 0.4 to 1.3 %, until an editor that had stood open since the morning was closed; from that second on it read 480 GB/s and 5750 to 5830 it/s, steady to 0.2 % (hardware check 96). The editor was idle and used 2.2 % of the 3D engine. A video playing in a browser makes the readings jump by 3 to 8 % from one to the next. Nobody thinks of an open editor as a heavy program, so telling people to close heavy programs does not reach it; the tool has to look. **How.** Windows keeps the card's use per process and engine (the `GPU Engine` performance counters, what Task Manager shows); `gpu_use` in `src/hw/gpu_users.cpp` reads them for three seconds, for the NVIDIA adapter only, without elevation. `busy_programs` in `src/core/busy_card.cpp` adds a program's processes and engines together and names what is at `kBusyPct` (0.5 %) or more: programs that only stand there read 0.01 to 0.05 %, the editor 2.2 % in one look and 1 % in another. Never named: the tool itself, and what Windows needs to draw the desktop, which nobody can close. A reading outside 0 to 100 % is left out; the counters give one now and then. **It asks, it does not refuse.** Another tuning tool writes to the card and a run is refused next to it. These programs change nothing on the card; they only make the numbers worse, and the user may have a reason to keep one open. So `busy_gate` asks through `OptimizeHooks::busy`: the window shows the list with **Check again**, **Go on anyway** and **Cancel**; the command line prints the list and does not start, unless `--busy-ok` is its last argument. The all-in-one run asks once, not in every step. **Nothing is closed by the tool**: a program may hold work that is not saved. **It is always written down.** Every look goes to the run's log (`other programs on the card: Trae (2 %)`, or `none`), also when the user goes on, so a log says what the card was shared with. That line would have saved the day this was found on. **A second look after an optimize run**, when the load is off the card again: when the programs differ from the ones before it (`busy_change`), the result carries a note that its numbers and the ones at stock were not measured on the same card. That is what happened in the run the editor was closed under: stock at 5321 it/s, the scan from then on at 5760. **Limits.** The look is taken while the card idles: under the run's own load the other programs' share reads near nothing, so the tool cannot watch during a run, only before and after. A program that is busy only now and then can be missed by a three-second look. An application that draws through an embedded browser is named as that browser (`msedgewebview2`), not by its own name; the process that owns it would have to be looked up. The second look is not built for the undervolt, the all-in-one and the retest runs. And where Windows does not give the counters, nothing is found and nothing is said: this is advice, not a check a run relies on. `gao --busy` shows what a run would find. |
| Edges are confirmed, then backed off | A 3 s probe can pass by luck; the edges get 30 s probes before the safety margin is applied, and the result must pass a 300 s soak. |
| The search climbs to a per-card bound | A fixed +300 MHz cap decided the result on cards that hold more. The bound is the offset range the driver reports, accepted only when it is plausible (spans stock, contains the applied offset, below a sanity limit). Bisecting such a range would start hundreds of MHz too high, so the search climbs from stock. |
| Both searches go one grid step at a time (15 MHz core, 50 MHz memory); nothing is bisected | The first value that fails is then exactly one step above the last one that passed, for an ordinary failure and for a driver reset alike, and the climb probes nothing more after a value that failed or reset the driver. The confirm probe then runs one step below an ordinary failure (at the last value that passed) and, after a reset, four steps below the last value that passed. The earlier scheme, 60 MHz strides and a bisect after the first failure, could leave the last passing value up to 45 MHz below the edge and bisected right below a value that had just reset the driver. The cost is more probes and a longer run; how much longer has not been measured. |
Expand Down
Loading