diff --git a/docs/assets/images/FullFTRvsCPF.png b/docs/assets/images/FullFTRvsCPF.png new file mode 100644 index 0000000..0b64735 Binary files /dev/null and b/docs/assets/images/FullFTRvsCPF.png differ diff --git a/docs/assets/images/FullFTvsCPF.png b/docs/assets/images/FullFTvsCPF.png new file mode 100644 index 0000000..17a0a88 Binary files /dev/null and b/docs/assets/images/FullFTvsCPF.png differ diff --git a/docs/assets/images/ZoomedFTRvsCPF.png b/docs/assets/images/ZoomedFTRvsCPF.png new file mode 100644 index 0000000..d1ca924 Binary files /dev/null and b/docs/assets/images/ZoomedFTRvsCPF.png differ diff --git a/docs/assets/images/ZoomedFTvsCPF.png b/docs/assets/images/ZoomedFTvsCPF.png new file mode 100644 index 0000000..0bb6a4f Binary files /dev/null and b/docs/assets/images/ZoomedFTvsCPF.png differ diff --git a/docs/index.markdown b/docs/index.markdown index 9dbddeb..fec782d 100644 --- a/docs/index.markdown +++ b/docs/index.markdown @@ -13,15 +13,17 @@ title: "Massive Scale Collision Detection with Bevy" # What is this? -An example codebase showing implementation of GPU accelerated collision detection (narrow-phase only) and standard CPU collision detection. Designed to realistically test the performance differences between each technique. You can run the code yourself and see the difference, or read this article where I discuss the results of my comparison testing. +1. An example codebase showing implementation of GPU accelerated collision detection (narrow-phase only) and standard CPU collision detection. Designed to realistically test the performance differences between each technique. You can run the code yourself and see the difference, here is the repo: + [![Repo](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/Sheldonfrith/gpu_accelerated_collision_detection) -## See the code: - -- link to github repo here with github icon +2. A report on the results of comparison testing using the above codebase. Here is the report... # TLDR -GPU acceleration is not hard to implement and can provide major performance increases over standard CPU collision detection. Significant improvements start at around 15k collisions per "frame" ("frame" = "iteration"/"step" if you are running a simulation and not a game) and reaching up to a 50% performance improvement for between 40k and 200k collisions per frame, with the improvements plateauing after that point. Current version of the code shows slight decreases in improvements for larger scales (25% at 10 million collisions per frame), but there are optimizations that can be done to the algorithm which would likely eliminate that decline with scale. +- GPU acceleration can provide major performance increases over CPU-based collision detection. + +- Significant improvements start at around 15k collisions per frame/iteration/step and reaching up to a 50% performance improvement for between 40k and 200k collisions per frame, with the improvements plateauing after that point. +- Current version of the code shows slight decreases in improvements for larger scales (25% at 10 million collisions per frame), but there are optimizations that can be done to the algorithm which would likely eliminate that decline with scale. ## Who this is useful for @@ -33,35 +35,52 @@ Especially if you are already using the [Bevy engine](https://bevyengine.org/), ## Rationale for Creating -I needed performant collision detection for a much larger scale than normal (hundreds of thousands of simultaneously colliding entities at least). I tried popular existing collision detection solutions (like Avian, Rapier) but my brief testing indicated they probably weren't built for massive simulations like I was working with (although they work great for most game applications). And I only needed collision detection, not a physics engine. +I needed performant collision detection for a much larger scale than normal; hundreds of thousands of simultaneously colliding entities, at least. I tried popular existing collision detection solutions, but my brief testing indicated that their performance was unacceptable for the scales I required. + +# Performance Results: + +### Raw Frame Time: + +![Full Frame Time vs Collisions per Frame Comparison Graph](/assets/images/FullFTvsCPF.png) + +And here is a zoomed version to show the critical point where GPU acceleration becomes valuable: +![Zoomed Frame Time vs Collisions per Frame Comparison Graph](/assets/images/ZoomedFTvsCPF.png) + +### % Frame Time Reduction using GPU: + +![Full Frame Time Reduction vs Collisions per Frame Graph](/assets/images/FullFTRvsCPF.png) +_Note the logarithmic scale of the x axis ABOVE._ + +And here is a zoomed version to show in more detail the point where GPU acceleration becomes valuable (NOT log scale): +![Zoomed Frame Time Reduction vs Collisions per Frame Graph](/assets/images/ZoomedFTRvsCPF.png) -# Caveats +# Caveats: - This technique will probably not provide benefits for web-based applications that do not have low level GPU access. -- If you are using this for a videogame that already has very intensive graphics, there might not be enough extra capacity on the GPU to handle this method. +- If you are using this for a videogame that already has _very_ intensive graphics, there might not be enough extra capacity on the GPU to handle this method. However for most games the extra GPU usage shouldn't be an issue. Collision detection for 160k collisions per frame, for example, used only about 7% of my GPU capacity (RTX 3070 laptop version). -# Narrow vs Broad Phase +## Narrow vs Broad Phase -As mentioned above this code is only for NARROW-PHASE of collision detection. If performance is an issue, you should first prioritize implementing a performant broad-phase to your collision detection as this is an easier way of making big performance gains. And for truly massive simulations a broad phase is required, because of the practical limits of narrow-phase collosion detection. +See [narrow vs broad phase](https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection#collision_performance). -#### Limits of Narrow-Only collision detection +This technique is only for **narrow-phase** of collision detection. If performance is an issue, you should first prioritize implementing a performant broad-phase to your collision detection as this is an easier way of making big performance gains. And for truly massive simulations a broad phase is required, because of the practical limits of narrow-phase collosion detection. -Using this program to test we can see that if you are using collision detection in a game if you are getting about 500k collisions per frame performance drops to between 10-20 fps under ideal conditions. So if you're game needs to handle that many collisions you have to implement some sort of broad-phase. +## Practical Upper Limits on Collisions/Frame -Even if you are running a scientific simulation and dont care about fps, you will still benefit greatly from implementing a broad-phase collision detection pass. +- around 200-300k collisions per frame for videogame applications. Total collisions can be much higher if you also implement broad-phase filtering (see above). +- tens or hundreds of millions of collisions per frame, limited only by the RAM available for storing all of the collision pairs. -## Discussion +# Further Performance Improvements -- Main reason GPU acceleration doesn't work as collision detection service is that number of collisions is unknown, but we have to pre-allocate memory when working with the GPU, leading to a lot of waste and slowdown -- Vs the CPU where we do not have to preallocate memory, so we only end up using the amount of memory necessary to hold the correct number of collisions +- The main waste with GPU based collision detection is having to pre-allocate a lot of memory which we don't actually end up using, since we don't know ahead of time the number of collisions that will be detected. Testing indicates anything that can be done to pre-estimate the number of collisions that will be detected yields large performance improvements (this is the purpose of the `max_detectable_collisions_scale` variable in the GPU code, but a lot of improvements can still be made to that part of the code). +- A major bottleneck is the render device's maximum storage buffer size. If there is a way to safely increase this buffer size limit, performance can be dramatically improved. (This may be hardware limited, I haven't had the time to look into it yet.) +- Switch to integer positions and integer math instead of floating point. This requires client code to use integer positions, which is less convenient, which is why the code currently uses floating point positions. +- For simulations, running batches in parallel may be possible as a method to utilize more of the GPU. -## How to improve GPU accelerated collision performance: +## Inlining -- If the maximum storage buffer size was much larger, this could be improved significantly -- Switch to integer positions and integer math instead of floating point -- If most or all of the simulation logic (movements and reactions to collisions) were moved to the GPU performance would improve and buffer size would not be a bottleneck -- run batches in parallel, since generally GPU is very underutilized, however this method requires some work to avoid stack overflows and memory shortages +You may notice that simply combining the collision processing with the collision detection can dramatically improve the CPU algorithm's speed so that it is actually faster than the GPU method. However we have to keep in mind that **the same thing can be said for the GPU algorithm**. If we also put collision processing directly onto the GPU we will also gain dramatic performance improvements. -## How to improve CPU accelerated collision performance: +If you are trying to get the absolute best possible performance in your application you will probably have to use this strategy, but otherwise you should avoid it because it creates highly coupled, difficult to maintain code. -- In the same way that moving more logic onto the GPU would improve performance by decreasing data transfer and memory allocation costs, inlining logic on the CPU side has the same benefits. This requires prior knowledge of the entire simulation, and leads to tightly coupled, non-reusable code. But the performance gains are very significant. +I have not done this for either CPU or GPU because I want this test to be representative of the general case, where we don't know what exactly the client is going to do with the collisions detected. diff --git a/output.json b/output.json index fa02592..c2214b3 100644 --- a/output.json +++ b/output.json @@ -571,4 +571,4 @@ "total_frames": 2500, "entities_spawned": 392 } -] \ No newline at end of file +] diff --git a/run_config.json b/run_config.json index c0eda2b..924a4ba 100644 --- a/run_config.json +++ b/run_config.json @@ -1,12 +1,12 @@ { - "bottom_left_x": -1, - "bottom_left_y": -1, - "top_right_x": 1, - "top_right_y": 1, - "sensor_radius": 21, - "body_radius": 3, - "rng_seed": 1, - "num_frames_to_test": 2500, - "use_gpu": false, - "path_to_output_json": "./output.json" - } \ No newline at end of file + "bottom_left_x": -10, + "bottom_left_y": -10, + "top_right_x": 10, + "top_right_y": 10, + "sensor_radius": 21, + "body_radius": 3, + "rng_seed": 1, + "num_frames_to_test": 1000, + "use_gpu": true, + "path_to_output_json": "./output.json" +} diff --git a/script.ipynb b/script.ipynb index be37460..a198b37 100644 --- a/script.ipynb +++ b/script.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -26,208 +26,202 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# test cases\n", "\n", "test_cases = [\n", - " # TestCase(\n", - " # width=3,\n", - " # height=3,\n", - " # frames=1000,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=12,\n", - " # height=12,\n", - " # frames=400,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=24,\n", - " # height=24,\n", - " # frames=200,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=40,\n", - " # height=40,\n", - " # frames=100,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=60,\n", - " # height=60,\n", - " # frames=20,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=80,\n", - " # height=80,\n", - " # frames=6,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=100,\n", - " # height=100,\n", - " # frames=4,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=120,\n", - " # height=120,\n", - " # frames=3,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=160,\n", - " # height=160,\n", - " # frames=3,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=18,\n", - " # height=18,\n", - " # frames=1000,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=13,\n", - " # height=13,\n", - " # frames=1500,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=15,\n", - " # height=15,\n", - " # frames=1400,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=16,\n", - " # height=16,\n", - " # frames=1700,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=17,\n", - " # height=17,\n", - " # frames=300,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=8,\n", - " # height=8,\n", - " # frames=3000,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=15,\n", - " # height=15,\n", - " # frames=300,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=16,\n", - " # height=16,\n", - " # frames=300,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=15,\n", - " # height=15,\n", - " # frames=5000,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=16,\n", - " # height=16,\n", - " # frames=5000,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=13,\n", - " # height=13,\n", - " # frames=4000,\n", - " # sensor_radius=5,\n", - " # body_radius=40\n", - " # ),\n", - " # TestCase(\n", - " # width=18,\n", - " # height=18,\n", - " # frames=2000,\n", - " # sensor_radius=5,\n", - " # body_radius=40\n", - " # ),\n", - " # TestCase(\n", - " # width=40,\n", - " # height=40,\n", - " # frames=5,\n", - " # sensor_radius=5,\n", - " # body_radius=40\n", - " # ),\n", - " # TestCase(\n", - " # width=9,\n", - " # height=9,\n", - " # frames=4000,\n", - " # sensor_radius=5,\n", - " # body_radius=40\n", - " # ),\n", - " # TestCase(\n", - " # width=10,\n", - " # height=10,\n", - " # frames=3500,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=11,\n", - " # height=11,\n", - " # frames=3000,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " # TestCase(\n", - " # width=14,\n", - " # height=14,\n", - " # frames=2500,\n", - " # sensor_radius=21,\n", - " # body_radius=3\n", - " # ),\n", - " TestCase(\n", - " width=2,\n", - " height=2,\n", + " TestCase(\n", + " width=3,\n", + " height=3,\n", + " frames=1000,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=12,\n", + " height=12,\n", + " frames=400,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=24,\n", + " height=24,\n", + " frames=200,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=40,\n", + " height=40,\n", + " frames=100,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=60,\n", + " height=60,\n", + " frames=20,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=80,\n", + " height=80,\n", + " frames=6,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=100,\n", + " height=100,\n", + " frames=4,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=120,\n", + " height=120,\n", + " frames=3,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=160,\n", + " height=160,\n", + " frames=3,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=18,\n", + " height=18,\n", + " frames=1000,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=13,\n", + " height=13,\n", + " frames=1500,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=15,\n", + " height=15,\n", + " frames=1400,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=16,\n", + " height=16,\n", + " frames=1700,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=17,\n", + " height=17,\n", + " frames=300,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=8,\n", + " height=8,\n", + " frames=3000,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=15,\n", + " height=15,\n", + " frames=300,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=16,\n", + " height=16,\n", + " frames=300,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=15,\n", + " height=15,\n", + " frames=5000,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=16,\n", + " height=16,\n", + " frames=5000,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=13,\n", + " height=13,\n", + " frames=4000,\n", + " sensor_radius=5,\n", + " body_radius=40\n", + " ),\n", + " TestCase(\n", + " width=18,\n", + " height=18,\n", + " frames=2000,\n", + " sensor_radius=5,\n", + " body_radius=40\n", + " ),\n", + " TestCase(\n", + " width=40,\n", + " height=40,\n", + " frames=5,\n", + " sensor_radius=5,\n", + " body_radius=40\n", + " ),\n", + " TestCase(\n", + " width=9,\n", + " height=9,\n", + " frames=4000,\n", + " sensor_radius=5,\n", + " body_radius=40\n", + " ),\n", + " TestCase(\n", + " width=10,\n", + " height=10,\n", + " frames=3500,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=11,\n", + " height=11,\n", + " frames=3000,\n", + " sensor_radius=21,\n", + " body_radius=3\n", + " ),\n", + " TestCase(\n", + " width=14,\n", + " height=14,\n", " frames=2500,\n", " sensor_radius=21,\n", " body_radius=3\n", " ),\n", + " \n", "]" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -283,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -292,20 +286,6 @@ "text": [ "Running test case 1 of 1\n" ] - }, - { - "ename": "CalledProcessError", - "evalue": "Command '['cargo', 'run', '--release']' returned non-zero exit status 101.", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mCalledProcessError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[16], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;66;03m# run\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m \u001b[43mrun_tests\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m./output.json\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", - "Cell \u001b[1;32mIn[4], line 22\u001b[0m, in \u001b[0;36mrun_tests\u001b[1;34m(rng_seed, path_to_output)\u001b[0m\n\u001b[0;32m 20\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, test_case \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(test_cases):\n\u001b[0;32m 21\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mRunning test case \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mi\u001b[38;5;241m+\u001b[39m\u001b[38;5;241m1\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m of \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlen\u001b[39m(test_cases)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m---> 22\u001b[0m \u001b[43mrun_test_one_side\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrng_seed\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpath_to_output\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtest_case\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 23\u001b[0m run_test_one_side(\u001b[38;5;28;01mTrue\u001b[39;00m, rng_seed, path_to_output, test_case)\n\u001b[0;32m 24\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTest case \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mi\u001b[38;5;241m+\u001b[39m\u001b[38;5;241m1\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m done\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "Cell \u001b[1;32mIn[4], line 46\u001b[0m, in \u001b[0;36mrun_test_one_side\u001b[1;34m(use_gpu, rng_seed, path_to_output, test_case)\u001b[0m\n\u001b[0;32m 33\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mopen\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrun_config.json\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mw\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[0;32m 34\u001b[0m f\u001b[38;5;241m.\u001b[39mwrite(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\"\"\u001b[39m\u001b[38;5;130;01m{{\u001b[39;00m\n\u001b[0;32m 35\u001b[0m \u001b[38;5;124m \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mbottom_left_x\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m: -\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mhalf_width\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m,\u001b[39m\n\u001b[0;32m 36\u001b[0m \u001b[38;5;124m \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mbottom_left_y\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m: -\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mhalf_height\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m,\u001b[39m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 44\u001b[0m \u001b[38;5;124m \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpath_to_output_json\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m: \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mpath_to_output\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 45\u001b[0m \u001b[38;5;124m \u001b[39m\u001b[38;5;130;01m}}\u001b[39;00m\u001b[38;5;124m\"\"\"\u001b[39m)\n\u001b[1;32m---> 46\u001b[0m \u001b[43msubprocess\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mcargo\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mrun\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m--release\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcheck\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n", - "File \u001b[1;32mC:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\\Lib\\subprocess.py:571\u001b[0m, in \u001b[0;36mrun\u001b[1;34m(input, capture_output, timeout, check, *popenargs, **kwargs)\u001b[0m\n\u001b[0;32m 569\u001b[0m retcode \u001b[38;5;241m=\u001b[39m process\u001b[38;5;241m.\u001b[39mpoll()\n\u001b[0;32m 570\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m check \u001b[38;5;129;01mand\u001b[39;00m retcode:\n\u001b[1;32m--> 571\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m CalledProcessError(retcode, process\u001b[38;5;241m.\u001b[39margs,\n\u001b[0;32m 572\u001b[0m output\u001b[38;5;241m=\u001b[39mstdout, stderr\u001b[38;5;241m=\u001b[39mstderr)\n\u001b[0;32m 573\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m CompletedProcess(process\u001b[38;5;241m.\u001b[39margs, retcode, stdout, stderr)\n", - "\u001b[1;31mCalledProcessError\u001b[0m: Command '['cargo', 'run', '--release']' returned non-zero exit status 101." - ] } ], "source": [ @@ -315,14 +295,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "C:\\Users\\sheld\\AppData\\Local\\Temp\\ipykernel_44744\\244706675.py:34: SettingWithCopyWarning: \n", + "C:\\Users\\sheld\\AppData\\Local\\Temp\\ipykernel_14440\\244706675.py:34: SettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", @@ -364,17 +344,65 @@ " # Calculate percentage difference\n", " pct_diff = -1* ((gpu_val - cpu_val) / cpu_val) * 100\n", " # add to df\n", - " df_gpu[\"pct_diff\"].iloc[i] = pct_diff" + " df_gpu[\"pct_diff\"].iloc[i] = pct_diff\n", + " \n", + " \n" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def deduplicate_clusters(df, cluster_width=5000):\n", + " \"\"\"\n", + " Remove all but one entry from each cluster, keeping the entry with the highest 'frames' value.\n", + " Clusters are defined as groups of entries where the difference in collisions_per_frame is less than cluster_width.\n", + " \n", + " Parameters:\n", + " df (pandas.DataFrame): DataFrame containing 'collisions_per_frame' and 'frames' columns\n", + " cluster_width (float): Maximum difference in collisions_per_frame to be considered part of the same cluster\n", + " \n", + " Returns:\n", + " pandas.DataFrame: DataFrame with only one entry per cluster\n", + " \"\"\"\n", + " df_sorted = df.sort_values('collisions_per_frame')\n", + " \n", + " # Initialize cluster labels\n", + " current_cluster = 0\n", + " cluster_labels = []\n", + " current_cluster_start = df_sorted['collisions_per_frame'].iloc[0]\n", + " \n", + " # Assign cluster labels\n", + " for value in df_sorted['collisions_per_frame']:\n", + " if value - current_cluster_start > cluster_width:\n", + " current_cluster += 1\n", + " current_cluster_start = value\n", + " cluster_labels.append(current_cluster)\n", + " \n", + " df_sorted['cluster'] = cluster_labels\n", + " result = df_sorted.loc[df_sorted.groupby('cluster')['frames'].idxmax()]\n", + " result = result.drop('cluster', axis=1).sort_index()\n", + " \n", + " return result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 30, "metadata": {}, "outputs": [ { "data": { - "image/png": "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48Zp586drVELAACAKabnyNxwww363e9+p5ycHA0aNEi+vr4O52+88UaXFQcAAHAupvda8vJquhPHYrGorq6uxUW5EnstAQDgenU2Q1l5JSquqFK34ACNiAmTt5fl/C90UqvttfTj260BAEDHkrGvQEvW5aigrMp+LMoaoNSkWI2Pi2rTWlgQDwAAOC1jX4Fmr8p2CDGSVFhWpdmrspWxr6BN63GqR+bpp5/Wb3/7WwUEBOjpp58+Z9t58+a5pDAAANC+1NkMLVmXo8bmpBiSLJKWrMvR2NhIlw4znYtTc2RiYmK0c+dOhYeHKyYmpumLWSw6fPiwSwtsKebIAADgGpmHTui257eft92rd45SQp/wFr2XS+fI5OXlNfpnAADQcRRXVJ2/kYl2rsAcGQAA4JRuwQEubecKBBkAAOCUETFhirIGqKnZLxaduXtpRExYm9VEkAEAAE7x9rIoNSlWkhqEmfrnqUmxbTbRVyLIAAAAE8bHRSl92lBFWh2HjyKtAUqfNrTN15ExvSAeAADo2MbHRWlsbGSrruzrrGb1yPzrX//StGnTlJCQoG+//VaS9PLLL+uTTz5xaXEAAKB98vayKKFPuCZefrES+oS7JcRIzQgyb731lsaNG6dOnTrpiy++UHV1tSSprKxMjz32mMsLBAAAaIrpIPPoo49q+fLlev755x12vh49erSys7NdWhwAAMC5mA4yBw4c0FVXXdXguNVqVWlpqStqAgAAcIrpIBMZGanc3NwGxz/55BP17t3bJUUBAAA4w3SQufPOO3Xvvffq888/l8Vi0fHjx/XKK69o4cKFmj17dmvUCAAA0CjTt1///ve/l81m03XXXadTp07pqquukr+/vxYuXKi5c+e2Ro0AAACNcmr368bU1NQoNzdXlZWVio2NVVBQkKtrcwl2vwYAwPO4dPfrs5WVlamurk5hYWGKjY21Hy8pKZGPjw9hAQAAtBnTc2RuvfVWrVmzpsHx119/XbfeeqtLigIAAHCG6SDz+eef69prr21w/JprrtHnn3/ukqIAAACcYTrIVFdX6/Tp0w2O19bW6r///a9LigIAAHCG6SAzYsQIPffccw2OL1++XMOGDXNJUQAAAM4wPdn30UcfVWJior788ktdd911kqTNmzdrx44d2rhxo8sLBAAAaIrpHpnRo0crMzNTPXr00Ouvv65169apb9++2rNnj6688srWqBEAAKBRzV5HxlOwjgwAAJ6n1daRkSSbzabc3FwVFxfLZrM5nGtsQ0kAAIDWYDrIbN++Xb/61a909OhR/bgzx2KxqK6uzmXFAQAAnIvpIHP33Xdr+PDhWr9+vaKiomSxWFqjLgAAgPMyHWQOHjyoN998U3379m2NegAAAJxm+q6lkSNHKjc3tzVqAQAAMMV0j8zcuXN13333qbCwUIMGDZKvr6/D+cGDB7usOAAAgHMxffu1l1fDThyLxSLDMNrlZF9uvwYAwPO02u3XeXl5LSoMAADAVUwHmV69erVGHQAAAKaZnuwrSS+//LJGjx6t6OhoHT16VJL01FNP6d1333VpcQAAAOdiOsikp6crJSVF119/vUpLS+1zYkJDQ/XUU0+ZulZaWpp+8pOfKDg4WN26ddOkSZN04MABhzZVVVVKTk5WeHi4goKCNHnyZBUVFZktGwAAXIBMB5lnnnlGzz//vP7whz/I29vbfnz48OHau3evqWtt27ZNycnJ2r59uzZt2qTa2lr97Gc/08mTJ+1tFixYoHXr1umNN97Qtm3bdPz4cd10001mywYAABegZk32jY+Pb3Dc39/fIYA4IyMjw+H5ypUr1a1bN+3atUtXXXWVysrK9Pe//12rV6/WmDFjJEkrVqzQwIEDtX37do0aNarBNaurq1VdXW1/Xl5ebqomAADgOUz3yMTExGj37t0NjmdkZGjgwIEtKqasrEySFBYWJknatWuXamtrlZiYaG8zYMAA9ezZU5mZmY1eIy0tTVar1f7o0aNHi2pqSp3NUOahE3p397fKPHRCdbYLehNxAADaJdM9MikpKUpOTlZVVZUMw1BWVpZeffVVpaWl6YUXXmh2ITabTfPnz9fo0aMVFxcnSSosLJSfn59CQ0Md2kZERKiwsLDR6yxatEgpKSn25+Xl5S4PMxn7CrRkXY4Kyqrsx6KsAUpNitX4uCiXvhcAAGia6SDzm9/8Rp06ddIDDzygU6dO6Ve/+pWio6P1l7/8RbfeemuzC0lOTta+ffv0ySefNPsa0pkhLn9//xZd41wy9hVo9qps/bj/pbCsSrNXZSt92lDCDAAAbcRUkDl9+rRWr16tcePGaerUqTp16pQqKyvVrVu3FhUxZ84cvf/++/r444/VvXt3+/HIyEjV1NSotLTUoVemqKhIkZGRLXrP5qizGVqyLqdBiJEkQ5JF0pJ1ORobGylvL3YFBwCgtZmaI+Pj46O7775bVVVnhlQCAwNbFGIMw9CcOXO0du1affTRR4qJiXE4P2zYMPn6+mrz5s32YwcOHFB+fr4SEhKa/b7NlZVX4jCc9GOGpIKyKmXllbRdUQAAdGCmh5ZGjBihL774wiUr/CYnJ2v16tV69913FRwcbJ/3YrVa1alTJ1mtVs2aNUspKSkKCwtTSEiI5s6dq4SEhEbvWGptxRVNh5jmtAMAAC1jOsjcc889uu+++/Tvf/9bw4YNU+fOnR3Om9n9Oj09XZJ0zTXXOBxfsWKFZs6cKUl68skn5eXlpcmTJ6u6ulrjxo3Ts88+a7Zsl+gWHODSdgAAoGXY/dqEOpuhKx7/SIVlVY3Ok7FIirQG6JP7xzBHBgCAFmD361bg7WVRalKsZq/KlkVyCDP1sSU1KZYQAwBAG2H3a5PGx0UpfdrQBuvIRLKODAAAbc50kJHO7H69fPly5eXlKTMzU7169dJTTz2lmJgYTZw40dU1tjvj46I0NjZSWXklKq6oUrfgAI2ICaMnBgCANubW3a89mbeXRQl9wjXx8ouV0CecEAMAgBu4dfdrAACAljAdZFy5+zUAAEBLtKvdrwEAAMxoN7tfAwAAmNVudr8GAAAwy6mVfd977z1NmDBBvr6+Dsddtft1a3Llyr4AAKBtOPv97dQcmV/84hcqLS2VJHl7e6u4uFhSy3e/BgAAaAmngkzXrl21fft2SbLvqQQAAOBuTs2RufvuuzVx4kRZLBZZLBZFRkY22ba9bRoJAAAuXE4FmcWLF+vWW29Vbm6ubrzxRq1YsUKhoaGtXBoAAMC5ORVk6if7DhgwQKmpqbr55psVGBjY2rUBAACck1N3LXl7e6uwsFBdu3aVt7e3CgoKPGaSL3ctAQDgeVx61xKTfRuqsxnKPHRC7+7+VpmHTqjOdt48CAAAXIzJvs2Qsa9AS9blqKCsyn4syhqg1KRYjY+LcmNlAAB0LE4NLUnS119/7dRk34kTJ7qyvhZz9dBSxr4CzV6VrR9/aPV9VOnThhJmAABoIWe/v53eomDAgAEdfrJvnc3QknU5DUKMJBk6E2aWrMvR2NhIeXsx/AYAQGszvft1ampqhwwxkpSVV+IwnPRjhqSCsipl5ZW0XVEAAHRgTvXIDB06VJs3b1aXLl0UHx9/zsm+2dnZLiuuvSmuaDrENKcdAABoGaeCzMSJE+Xv7y9JmjRpUmvW0651Cw5waTsAANAyTk/29VSunOxbZzN0xeMfqbCsqtF5MhZJkdYAfXL/GObIAADQAi6f7FvPMAzt2rVLR44ckcViUUxMzHmHmy4U3l4WpSbFavaqbFkkhzBT/7dPTYolxAAA0EZMBZktW7Zo1qxZOnr0qOo7curDzIsvvqirrrqqVYpsT8bHRSl92tAG68hEso4MAABtzumhpdzcXA0ZMkQjR47UvffeqwEDBsgwDOXk5Ojpp5/Wzp07tWfPHvXu3bu1azaltbYoqLMZysorUXFFlboFB2hETBg9MQAAuIiz399OB5k5c+Zo//792rx5c4NzhmEoMTFRsbGxeuaZZ5pfdStgryUAADyPS/dakqStW7dq/vz5jZ6zWCyaP3++tmzZYrpQAACA5nI6yOTn52vQoEFNno+Li9PRo0ddUhQAAIAznA4ylZWV51zRNzAwUKdOnXJJUQAAAM4wdddSTk6OCgsLGz333XffuaQgAAAAZ5kKMtddd50amxtssVhkGEaHWEsGAAC0H04Hmby8vNasAwAAwDSng0yvXr1asw4AAADTnJ7sCwAA0N4QZAAAgMciyAAAAI9FkAEAAB7L1O3XP/bdd9/p888/V11dnX7yk58oKoqdnwEAQNtpdpB56623NGvWLPXv31+1tbU6cOCAli1bpttvv92V9bVb7H4NAID7Ob37dWVlpYKCguzPBw8erDfffFP9+/eXJK1fv1533nmnjh8/3jqVNlNr7H6dsa9AS9blqKCsyn4syhqg1KRYjY+jVwoAgJZy+e7Xw4YN07vvvmt/7uPjo+LiYvvzoqIi+fn5NbNcz5Gxr0CzV2U7hBhJKiyr0uxV2crYV+CmygAA6Hic7pE5cuSIkpOT5efnp2XLlunQoUO69dZbVVdXp9OnT8vLy0srV67U9ddf39o1m+LKHpk6m6ErHv+oQYipZ5EUaQ3QJ/ePYZgJAIAWcPb72+k5MpdcconWr1+vV199VVdffbXmzZun3Nxc5ebmqq6uTgMGDFBAQIBLim+vsvJKmgwxkmRIKiirUlZeiRL6hLddYQAAdFCmb7++7bbbtGPHDn355Ze65pprZLPZdPnll1/wIUaSiiuaDjHNaQcAAFrG1F1LH3zwgfbv368hQ4bohRde0LZt2zR16lRNmDBBDz/8sDp16tRadbYL3YKdC2vOtgMAAC3jdI/Mfffdp9tvv107duzQXXfdpUceeURXX321srOzFRAQoPj4eH344YetWavbjYgJU5Q1QE3NfrHozN1LI2LC2rIsAAA6LKcn+4aHh2vjxo0aNmyYSkpKNGrUKH3zzTf28zk5Obrrrrv0r3/9q9WKbQ5X335df9eSdGZOTL36cJM+bSi3YAMA0EIuv/26c+fOysvLkyQdO3aswZyY2NjYdhdiWsP4uCilTxuqSKvj3z/SGkCIAQCgjTndI/PKK6/ozjvvVGhoqE6dOqV//OMfmjhxYmvX12KtsSCexMq+AAC0Jme/v50OMpJ04sQJHT58WP369VNoaKgr6mx1rRVkAABA63H5OjLSmXky4eGsjwIAANoH0+vIAAAAtBduDTIff/yxkpKSFB0dLYvFonfeecfhvGEYeuihhxQVFaVOnTopMTFRBw8edE+xAACg3XFrkDl58qSGDBmiZcuWNXr+T3/6k55++mktX75cn3/+uTp37qxx48apqoqVcwEAgMk5Mq42YcIETZgwodFzhmHoqaee0gMPPGC/O+qll15SRESE3nnnHd16662Nvq66ulrV1dX25+Xl5a4vHAAAtAvtdo5MXl6eCgsLlZiYaD9mtVo1cuRIZWZmNvm6tLQ0Wa1W+6NHjx5tUS4AAHCDdhtkCgsLJUkREREOxyMiIuznGrNo0SKVlZXZH8eOHWvVOgEAgPu4dWipNfj7+8vf39/dZQAAgDbQbntkIiMjJUlFRUUOx4uKiuznAABAx9Zug0xMTIwiIyO1efNm+7Hy8nJ9/vnnSkhIcGNlAACgvXDr0FJlZaVyc3Ptz/Py8rR7926FhYWpZ8+emj9/vh599FH169dPMTExevDBBxUdHa1Jkya5r2gAANBuuDXI7Ny5U9dee639eUpKiiRpxowZWrlypf7nf/5HJ0+e1G9/+1uVlpbqiiuuUEZGRoOdtwEAQMdkatNIT8SmkQAAeB5nv7/b7RwZAACA8yHIAAAAj0WQAQAAHosgAwAAPBZBBgAAeCyCDAAA8FgEGQAA4LEIMgAAwGMRZAAAgMciyAAAAI9FkAEAAB6LIAMAADwWQQYAAHgsggwAAPBYBBkAAOCxCDIAAMBjEWQAAIDHIsgAAACPRZABAAAeiyADAAA8FkEGAAB4LIIMAADwWAQZAADgsQgyAADAYxFkAACAxyLIAAAAj+Xj7gI8VZ3NUFZeiYorqtQtOEAjYsLk7WVxd1kAAHQoBJlmyNhXoCXrclRQVmU/FmUNUGpSrMbHRbmxMgAAOhaGlkzK2Feg2auyHUKMJBWWVWn2qmxl7CtwU2UAAHQ8BBkT6myGlqzLkdHIufpjS9blqM7WWAsAAOBqBBkTsvJKGvTEnM2QVFBWpay8krYrCgCADowgY0JxRdMhpjntAABAyxBkTOgWHODSdgAAoGUIMiaMiAlTlDVATd1kbdGZu5dGxIS1ZVkAAHRYBBkTvL0sSk2KlaQGYab+eWpSLOvJAADQRggyJo2Pi1L6tKGKtDoOH0VaA5Q+bSjryAAA0IZYEK8ZxsdFaWxsJCv7AgDgZgSZZvL2siihT7i7ywAAoENjaAkAAHgsggwAAPBYBBkAAOCxCDIAAMBjEWQAAIDHIsgAAACPRZABAAAeiyADAAA8FkEGAAB4LIIMAADwWAQZAADgsQgyAADAYxFkAACAx2L362aqsxnKyitRcUWVugUHaERMmLy9LO4uCwCADoUg0wwf7CnQA+/uU8nJGvuxKGuAUpNiNT4uyo2VAQDQsXjE0NKyZct0ySWXKCAgQCNHjlRWVpbbakn7IEf3rM52CDGSVFBWpdmrspWxr8BNlQEA0PG0+yDz2muvKSUlRampqcrOztaQIUM0btw4FRcXt3ktH+w5rr99nNfkeUPSknU5qrMZbVcUAAAdWLsPMn/+859155136vbbb1dsbKyWL1+uwMBAvfjii21aR53N0O/e2nPedgVlVcrKK2mDigAAQLsOMjU1Ndq1a5cSExPtx7y8vJSYmKjMzMxGX1NdXa3y8nKHhytsP3RCJ6vrnGpbWF7lkvcEAADn1q6DzHfffae6ujpFREQ4HI+IiFBhYWGjr0lLS5PVarU/evTo4ZJaPjv8ndNtSyqrXfKeAADg3Np1kGmORYsWqayszP44duyYS657/Pv/Ot02rLOfS94TAACcW7u+/fqiiy6St7e3ioqKHI4XFRUpMjKy0df4+/vL39/f5bVEh3Zyum2k1fm2AACg+dp1j4yfn5+GDRumzZs324/ZbDZt3rxZCQkJbVrLT/tc5FS7sM5+GhET1srVAAAAqZ33yEhSSkqKZsyYoeHDh2vEiBF66qmndPLkSd1+++1tWseoPuEKDfRV6anac7Z7dGIcK/wCANBG2n2QmTJliv7zn//ooYceUmFhoS6//HJlZGQ0mADc2ry9LFp60yDdvSq7yTZ3XRWj6wezsi8AAG3FYhjGBb16W3l5uaxWq8rKyhQSEtLi62XsK9Di975SYfkPdyZ1CfTVHyfF6frB0S2+PgAAcP77u933yLQ34+OiNDY2kg0jAQBoBwgyzeDtZVFCn3B3lwEAQIfXru9aAgAAOBeCDAAA8FgEGQAA4LEIMgAAwGMRZAAAgMciyAAAAI9FkAEAAB6LIAMAADwWQQYAAHisC35l3/qtpMrLy91cCQAAcFb99/b5toS84INMRUWFJKlHjx5urgQAAJhVUVEhq9Xa5PkLfvdrm82m48ePKzg4WBaL6zZ2LC8vV48ePXTs2DGX7KrtyfgsfsBncQafww/4LH7AZ/EDPoszzvU5GIahiooKRUdHy8ur6ZkwF3yPjJeXl7p3795q1w8JCenQ/xGejc/iB3wWZ/A5/IDP4gd8Fj/gszijqc/hXD0x9ZjsCwAAPBZBBgAAeCyCTDP5+/srNTVV/v7+7i7F7fgsfsBncQafww/4LH7AZ/EDPoszXPE5XPCTfQEAwIWLHhkAAOCxCDIAAMBjEWQAAIDHIsgAAACPRZBppmXLlumSSy5RQECARo4cqaysLHeX1OY+/vhjJSUlKTo6WhaLRe+88467S3KLtLQ0/eQnP1FwcLC6deumSZMm6cCBA+4uyy3S09M1ePBg++JWCQkJ+vDDD91dltstXbpUFotF8+fPd3cpbrF48WJZLBaHx4ABA9xdllt8++23mjZtmsLDw9WpUycNGjRIO3fudHdZbe6SSy5p8N+ExWJRcnKy6WsRZJrhtddeU0pKilJTU5Wdna0hQ4Zo3LhxKi4udndpberkyZMaMmSIli1b5u5S3Grbtm1KTk7W9u3btWnTJtXW1upnP/uZTp486e7S2lz37t21dOlS7dq1Szt37tSYMWM0ceJEffXVV+4uzW127Nihv/3tbxo8eLC7S3Gryy67TAUFBfbHJ5984u6S2tz333+v0aNHy9fXVx9++KFycnL0xBNPqEuXLu4urc3t2LHD4b+HTZs2SZJuvvlm8xczYNqIESOM5ORk+/O6ujojOjraSEtLc2NV7iXJWLt2rbvLaBeKi4sNSca2bdvcXUq70KVLF+OFF15wdxluUVFRYfTr18/YtGmTcfXVVxv33nuvu0tyi9TUVGPIkCHuLsPt7r//fuOKK65wdxnt0r333mv06dPHsNlspl9Lj4xJNTU12rVrlxITE+3HvLy8lJiYqMzMTDdWhvairKxMkhQWFubmStyrrq5Oa9as0cmTJ5WQkODuctwiOTlZN9xwg8O/Fx3VwYMHFR0drd69e2vq1KnKz893d0lt7r333tPw4cN18803q1u3boqPj9fzzz/v7rLcrqamRqtWrdIdd9zRrM2dCTImfffdd6qrq1NERITD8YiICBUWFrqpKrQXNptN8+fP1+jRoxUXF+fuctxi7969CgoKkr+/v+6++26tXbtWsbGx7i6rza1Zs0bZ2dlKS0tzdyluN3LkSK1cuVIZGRlKT09XXl6errzySlVUVLi7tDZ1+PBhpaenq1+/ftqwYYNmz56tefPm6R//+Ie7S3Ord955R6WlpZo5c2azXn/B734NtKXk5GTt27evQ47/17v00ku1e/dulZWV6c0339SMGTO0bdu2DhVmjh07pnvvvVebNm1SQECAu8txuwkTJtj/PHjwYI0cOVK9evXS66+/rlmzZrmxsrZls9k0fPhwPfbYY5Kk+Ph47du3T8uXL9eMGTPcXJ37/P3vf9eECRMUHR3drNfTI2PSRRddJG9vbxUVFTkcLyoqUmRkpJuqQnswZ84cvf/++9qyZYu6d+/u7nLcxs/PT3379tWwYcOUlpamIUOG6C9/+Yu7y2pTu3btUnFxsYYOHSofHx/5+Pho27Ztevrpp+Xj46O6ujp3l+hWoaGh6t+/v3Jzc91dSpuKiopqEOgHDhzYIYfZ6h09elT//Oc/9Zvf/KbZ1yDImOTn56dhw4Zp8+bN9mM2m02bN2/usPMAOjrDMDRnzhytXbtWH330kWJiYtxdUrtis9lUXV3t7jLa1HXXXae9e/dq9+7d9sfw4cM1depU7d69W97e3u4u0a0qKyt16NAhRUVFubuUNjV69OgGSzN888036tWrl5sqcr8VK1aoW7duuuGGG5p9DYaWmiElJUUzZszQ8OHDNWLECD311FM6efKkbr/9dneX1qYqKysdflHl5eVp9+7dCgsLU8+ePd1YWdtKTk7W6tWr9e677yo4ONg+V8pqtapTp05urq5tLVq0SBMmTFDPnj1VUVGh1atXa+vWrdqwYYO7S2tTwcHBDeZIde7cWeHh4R1y7tTChQuVlJSkXr166fjx40pNTZW3t7duu+02d5fWphYsWKCf/vSneuyxx3TLLbcoKytLzz33nJ577jl3l+YWNptNK1as0IwZM+Tj04I44vqbqDqGZ555xujZs6fh5+dnjBgxwti+fbu7S2pzW7ZsMSQ1eMyYMcPdpbWpxj4DScaKFSvcXVqbu+OOO4xevXoZfn5+RteuXY3rrrvO2Lhxo7vLahc68u3XU6ZMMaKiogw/Pz/j4osvNqZMmWLk5ua6uyy3WLdunREXF2f4+/sbAwYMMJ577jl3l+Q2GzZsMCQZBw4caNF1LIZhGC3LVAAAAO7BHBkAAOCxCDIAAMBjEWQAAIDHIsgAAACPRZABAAAeiyADAAA8FkEGAAB4LIIMAAAw7eOPP1ZSUpKio6NlsVj0zjvvmHr94sWLZbFYGjw6d+5s6joEGaCDWrx4sS6//HL785kzZ2rSpEn259dcc43mz5/v1LXMtL1QLF68WBEREc36Bxy4EJw8eVJDhgzRsmXLmvX6hQsXqqCgwOERGxurm2++2dR12GsJ8ECFhYX64x//qPXr1+vbb79Vt27ddPnll2v+/Pm67rrrXPIeb7/9tnx9fV3e9kKwf/9+LVmyRGvXrtWoUaPUpUsXd5cEtLkJEyZowoQJTZ6vrq7WH/7wB7366qsqLS1VXFycHn/8cV1zzTWSpKCgIAUFBdnbf/nll8rJydHy5ctN1UGQATzMkSNHNHr0aIWGhup///d/NWjQINXW1mrDhg1KTk7W119/7ZL3CQsLa5W2nqSmpkZ+fn4Njh86dEiSNHHiRFksFlOvBTqKOXPmKCcnR2vWrFF0dLTWrl2r8ePHa+/everXr1+D9i+88IL69++vK6+80tT7MLQEeJh77rlHFotFWVlZmjx5svr376/LLrtMKSkp2r59u71dfn6+Jk6cqKCgIIWEhOiWW25RUVGR0+/z4+GiZ599Vv369VNAQIAiIiL0y1/+ssm233//vaZPn64uXbooMDBQEyZM0MGDB+3nV65cqdDQUG3YsEEDBw5UUFCQxo8fr4KCAnubrVu3asSIEercubNCQ0M1evRoHT16tNFajxw5IovFojVr1uinP/2pAgICFBcXp23btjm027dvnyZMmKCgoCBFRETo17/+tb777juHv8ecOXM0f/58XXTRRRo3blyD91q8eLGSkpIkSV5eXvYgUz8098c//lHR0dG69NJLJUkvv/yyhg8fruDgYEVGRupXv/qViouLHf6eFotFGzZsUHx8vDp16qQxY8aouLhYH374oQYOHKiQkBD96le/0qlTp+yvs9lsSktLU0xMjDp16qQhQ4bozTffbPTzAdpafn6+VqxYoTfeeENXXnml+vTpo4ULF+qKK67QihUrGrSvqqrSK6+8olmzZpl+L4IM4EFKSkqUkZGh5OTkRifEhYaGSjrzJTdx4kSVlJRo27Zt2rRpkw4fPqwpU6Y063137typefPm6eGHH9aBAweUkZGhq666qsn2M2fO1M6dO/Xee+8pMzNThmHo+uuvV21trb3NqVOn9H//9396+eWX9fHHHys/P18LFy6UJJ0+fVqTJk3S1VdfrT179igzM1O//e1vm+z9qPe73/1O9913n7744gslJCQoKSlJJ06ckCSVlpZqzJgxio+P186dO5WRkaGioiLdcsstDtf4xz/+IT8/P3366aeNdnEvXLjQ/g9x/bh+vc2bN+vAgQPatGmT3n//fUlSbW2tHnnkEX355Zd65513dOTIEc2cObPBdRcvXqy//vWv+uyzz3Ts2DHdcssteuqpp7R69WqtX79eGzdu1DPPPGNvn5aWppdeeknLly/XV199pQULFmjatGkNwhvgDnv37lVdXZ369+9vH0IKCgrStm3b7D2aZ1u7dq0qKio0Y8YM82/miq24AbSNzz//3JBkvP322+dst3HjRsPb29vIz8+3H/vqq68MSUZWVpZhGIaRmppqDBkyxH5+xowZxsSJE+3Pr776auPee+81DMMw3nrrLSMkJMQoLy9v9P3ObvvNN98YkoxPP/3Ufv67774zOnXqZLz++uuGYRjGihUrDElGbm6uvc2yZcuMiIgIwzAM48SJE4YkY+vWref+QP5/eXl5hiRj6dKl9mO1tbVG9+7djccff9wwDMN45JFHjJ/97GcOrzt27JghyThw4ID97xEfH3/e91u7dq3x438+Z8yYYURERBjV1dXnfO2OHTsMSUZFRYVhGIaxZcsWQ5Lxz3/+094mLS3NkGQcOnTIfuyuu+4yxo0bZxiGYVRVVRmBgYHGZ5995nDtWbNmGbfddtt56wdcTZKxdu1a+/M1a9YY3t7extdff20cPHjQ4VFQUNDg9WPGjDEmTZrUrPdmjgzgQc78e3F++/fvV48ePdSjRw/7sdjYWIWGhmr//v36yU9+Yup9x44dq169eql3794aP368xo8fr1/84hcKDAxs9L19fHw0cuRI+7Hw8HBdeuml2r9/v/1YYGCg+vTpY38eFRVlH3IJCwvTzJkzNW7cOI0dO1aJiYm65ZZbFBUVdc46ExIS7H/28fHR8OHD7e/55ZdfasuWLQ6TC+sdOnRI/fv3lyQNGzbMmY+kUYMGDWowL2bXrl1avHixvvzyS33//fey2WySznS9x8bG2tsNHjzY/ueIiAgFBgaqd+/eDseysrIkSbm5uTp16pTGjh3r8F41NTWKj49vdv2Aq8THx6uurk7FxcXnnfOSl5enLVu26L333mvWexFkAA/Sr18/WSwWl03odVZwcLCys7O1detWbdy4UQ899JAWL16sHTt22IezzPrxXU4Wi8UhqK1YsULz5s1TRkaGXnvtNT3wwAPatGmTRo0a1az3q6ysVFJSkh5//PEG584OSGbXsDjbj1978uRJjRs3TuPGjdMrr7yirl27Kj8/X+PGjVNNTY1D27M/D4vF0ujnUx+CKisrJUnr16/XxRdf7NDO39+/2fUDZlRWVio3N9f+PC8vT7t371ZYWJj69++vqVOnavr06XriiScUHx+v//znP9q8ebMGDx6sG264wf66F198UVFRUee8A+pcmCMDeJCwsDCNGzdOy5Yt08mTJxucLy0tlSQNHDhQx44d07Fjx+zncnJyVFpa6tALYIaPj48SExP1pz/9SXv27NGRI0f00UcfNWg3cOBAnT59Wp9//rn92IkTJ3TgwAHT7x0fH69Fixbps88+U1xcnFavXn3O9mdPdj59+rR27dqlgQMHSpKGDh2qr776Spdccon69u3r8GhJeDmXr7/+WidOnNDSpUt15ZVXasCAAQ4TfZsrNjZW/v7+ys/Pb/B3ObsXDmhNO3fuVHx8vL0XMCUlRfHx8XrooYcknfkxMn36dN1333269NJLNWnSJO3YsUM9e/a0X8Nms2nlypWaOXOmvL29m1UHPTKAh1m2bJlGjx6tESNG6OGHH9bgwYN1+vRpbdq0Senp6dq/f78SExM1aNAgTZ06VU899ZROnz6te+65R1dffbWGDx9u+j3ff/99HT58WFdddZW6dOmiDz74QDabzX5nztn69euniRMn6s4779Tf/vY3BQcH6/e//70uvvhiTZw40an3y8vL03PPPacbb7xR0dHROnDggA4ePKjp06ef97Pp16+fBg4cqCeffFLff/+97rjjDklScnKynn/+ed122236n//5H4WFhSk3N1dr1qzRCy+80Ox/RM+lZ8+e8vPz0zPPPKO7775b+/bt0yOPPNLi6wYHB2vhwoVasGCBbDabrrjiCpWVlenTTz9VSEhI8yZMAiZdc8015xzu9vX11ZIlS7RkyZIm23h5eTn84GoOemQAD9O7d29lZ2fr2muv1X333ae4uDiNHTtWmzdvVnp6uqQzwxDvvvuuunTpoquuukqJiYnq3bu3XnvttWa9Z2hoqN5++22NGTNGAwcO1PLly/Xqq6/qsssua7T9ihUrNGzYMP385z9XQkKCDMPQBx984PSieYGBgfr666/tt5f/9re/VXJysu66665zvm7p0qVaunSphgwZok8++UTvvfeeLrroIklSdHS0Pv30U9XV1elnP/uZBg0apPnz5ys0NFReXq3zT2HXrl21cuVKvfHGG4qNjdXSpUv1f//3fy659iOPPKIHH3xQaWlpGjhwoMaPH6/169crJibGJdcHPIXFcHb2IAC0U0eOHFFMTIy++OILh20XAFz46JEBAAAeiyADAAA8FkNLAADAY9EjAwAAPBZBBgAAeCyCDAAA8FgEGQAA4LEIMgAAwGMRZAAAgMciyAAAAI9FkAEAAB7r/wMjaqV1Mot4HgAAAABJRU5ErkJggg==", + "image/png": "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", "text/plain": [ "
" ] @@ -385,16 +413,23 @@ ], "source": [ "# plot % difference\n", - "n = 1000000000 # for example\n", + "n = 100000 # for example\n", "\n", "# Filter the DataFrame for collisions_per_frame < n\n", "df_gpu_filtered = df_gpu[df_gpu[\"collisions_per_frame\"] < n]\n", + "\n", + "df_gpu_filtered_deduped = deduplicate_clusters(df_gpu_filtered, cluster_width=500)\n", + "# reindex\n", + "df_gpu_filtered_deduped = df_gpu_filtered_deduped.reset_index(drop=True)\n", + "# sort again\n", + "df_gpu_filtered_deduped = df_gpu_filtered_deduped.sort_values('collisions_per_frame')\n", "plt.figure()\n", - "plt.scatter(df_gpu_filtered[\"collisions_per_frame\"], df_gpu_filtered[\"pct_diff\"], label=\"Gpu\")\n", + "plt.plot(df_gpu_filtered_deduped[\"collisions_per_frame\"], df_gpu_filtered_deduped[\"pct_diff\"], label=\"Gpu\")\n", "# plt.xscale('log')\n", "plt.xlabel(\"Collisions per frame\")\n", - "plt.ylabel(\"% Difference in frame time\")\n", - "plt.legend()\n", + "plt.ylabel(\"% Reduction in frame time\")\n", + "plt.title(\"Reduction in frame time vs. Collisions per frame\")\n", + "# plt.legend()\n", "plt.show()\n", "\n" ] @@ -451,6 +486,9 @@ "\n", "# Your existing data loading code...\n", "\n", + "# deduplicate_clusters\n", + "df_gpu_deduped = deduplicate_clusters(df_gpu, cluster_width=500)\n", + "\n", "plt.figure(figsize=(12, 6)) # Make the figure a bit wider to accommodate annotations\n", "\n", "# Plot the original lines\n", diff --git a/src/collision_detection_performance_test.rs b/src/collision_detection_performance_test.rs index 17161d2..f10f138 100644 --- a/src/collision_detection_performance_test.rs +++ b/src/collision_detection_performance_test.rs @@ -18,6 +18,7 @@ use crate::{ entity_movement::{move_entities_deterministic, setup_position_cache}, entity_spawning::spawn_entities, graphics::plugin::GraphicsPlugin, + headless_entity_spawning::spawn_entities_headless, performance::{PerformanceMetrics, track_performance_and_exit}, }; diff --git a/src/headless_entity_spawning.rs b/src/headless_entity_spawning.rs new file mode 100644 index 0000000..e4416a2 --- /dev/null +++ b/src/headless_entity_spawning.rs @@ -0,0 +1,48 @@ +use bevy::{ + asset::{Assets, RenderAssetUsages}, + log, + math::{Vec2, Vec3, bounding::BoundingCircle}, + prelude::{Commands, Mesh, Mesh2d, Res, ResMut, Transform}, + sprite::{ColorMaterial, MeshMaterial2d}, + utils::default, +}; + +use crate::{ + components_and_resources::{BoundingCircleComponent, EntitiesSpawned, Sensor}, + config::RunConfig, + graphics::colors_and_handles::{AvailableColor, ColorHandles}, +}; + +pub fn spawn_entities_headless(mut commands: Commands, run_config: Res) { + let mut count = 0; + for x in run_config.bottom_left_x..run_config.top_right_x { + for y in run_config.bottom_left_y..run_config.top_right_y { + spawn_body_headless(x as f32, y as f32, run_config.body_radius, &mut commands); + spawn_sensor_headless(x as f32, y as f32, run_config.sensor_radius, &mut commands); + count += 2; + } + } + log::info!("total of {} entities spawned", count); + commands.insert_resource(EntitiesSpawned(count)); +} + +fn spawn_body_headless(x: f32, y: f32, radius: f32, commands: &mut Commands) { + commands.spawn(( + Transform { + translation: Vec3::new(x, y, 0.0), + ..default() + }, + BoundingCircleComponent(BoundingCircle::new(Vec2::new(x, y), radius)), + )); +} + +fn spawn_sensor_headless(x: f32, y: f32, radius: f32, commands: &mut Commands) { + commands.spawn(( + Sensor {}, + Transform { + translation: Vec3::new(x, y, 0.0), + ..default() + }, + BoundingCircleComponent(BoundingCircle::new(Vec2::new(x, y), radius)), + )); +} diff --git a/src/main.rs b/src/main.rs index 17fd6bb..dc3bc6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,8 +14,10 @@ pub mod entity_movement; pub mod entity_spawning; pub mod gpu_collision_detection; pub mod graphics; +pub mod headless_entity_spawning; pub mod helpers; pub mod performance; + fn main() { let path_to_run_config_json = "./run_config.json"; let run_config = serde_json::from_str::(