Skip to content

Commit f978433

Browse files
AbsolucyLemonInTheDarkjlsnow301
authored
[PORT] sorting code improvements+optimizations (#4797)
* Micros timSort slightly (#74889) ## About The Pull Request Datum var reads are expensive, and sorting code does a lot of them. let's work on that together, as a group. There's maybe 250ms of sorting cost sitting in mostly global variable work. I'd like to start chopping at that. My tracy profiles aren't the most helpful, estimating this to save about 14% of timSort over the course of init, tho that's slightly noisy and not the most reliable ## Why It's Good For The Game speed * Code cleanup: Sorting (#83017) 1. Removes code duplication 2. Fully documents `sortTim()` 3. Makes a define with default sortTim behavior, straight and to the point for 95% of cases 4. Migrates other sorts into the same file 5. Removes some redundancy where they're reassigning a variable using an in place sorter For the record, we only use timSort More documentation, easier to read, uses `length` over `len`, etc Should be no gameplay effect at all --------- Co-authored-by: LemonInTheDark <[email protected]> Co-authored-by: Jeremiah <[email protected]>
1 parent 7620fa3 commit f978433

File tree

16 files changed

+116
-75
lines changed

16 files changed

+116
-75
lines changed

code/__HELPERS/hallucinations.dm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ GLOBAL_LIST_INIT(random_hallucination_weighted_list, generate_hallucination_weig
149149
last_type_weight = this_weight
150150

151151
// Sort by weight descending, where weight is the values (not the keys). We assoc_to_keys later to get JUST the text
152-
all_weights = sortTim(all_weights, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE)
152+
sortTim(all_weights, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE)
153153

154154
var/page_style = "<style>table, th, td {border: 1px solid black;border-collapse: collapse;}</style>"
155155
var/page_contents = "[page_style]<table style=\"width:100%\">[header][jointext(assoc_to_keys(all_weights), "")]</table>"

code/__HELPERS/sorts/InsertSort.dm

Lines changed: 0 additions & 19 deletions
This file was deleted.

code/__HELPERS/sorts/MergeSort.dm

Lines changed: 0 additions & 19 deletions
This file was deleted.

code/__HELPERS/sorts/TimSort.dm

Lines changed: 0 additions & 20 deletions
This file was deleted.

code/__HELPERS/sorts/helpers.dm

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/// Sorts the list in place with timSort, default settings.
2+
#define SORT_TIM(to_sort, associative) if(length(to_sort) >= 2) { \
3+
var/datum/sort_instance/sorter = GLOB.sortInstance; \
4+
if (isnull(sorter)) { \
5+
sorter = new; \
6+
} \
7+
sorter.L = to_sort; \
8+
sorter.cmp = GLOBAL_PROC_REF(cmp_numeric_asc); \
9+
sorter.associative = associative; \
10+
sorter.timSort(1, 0); \
11+
}
12+
13+
14+
/// Helper for the sorting procs. Prevents some code duplication. Creates /datum/sort_instance/sorter
15+
#define CREATE_SORT_INSTANCE(to_sort, cmp, associative, fromIndex, toIndex) \
16+
if(length(to_sort) < 2) { \
17+
return to_sort; \
18+
} \
19+
fromIndex = fromIndex % length(to_sort); \
20+
toIndex = toIndex % (length(to_sort) + 1); \
21+
if (fromIndex <= 0) { \
22+
fromIndex += length(to_sort); \
23+
} \
24+
if (toIndex <= 0) { \
25+
toIndex += length(to_sort) + 1; \
26+
} \
27+
var/datum/sort_instance/sorter = GLOB.sortInstance; \
28+
if (isnull(sorter)) { \
29+
sorter = new; \
30+
} \
31+
sorter.L = to_sort; \
32+
sorter.cmp = cmp; \
33+
sorter.associative = associative;
34+
35+
36+
/**
37+
* ## Tim Sort
38+
* Hybrid sorting algorithm derived from merge sort and insertion sort.
39+
*
40+
* **Sorts in place**.
41+
* You might not need to get the return value.
42+
*
43+
* @see
44+
* https://en.wikipedia.org/wiki/Timsort
45+
*
46+
* @param {list} to_sort - The list to sort.
47+
*
48+
* @param {proc} cmp - The comparison proc to use. Default: Numeric ascending.
49+
*
50+
* @param {boolean} associative - Whether the list is associative. Default: FALSE.
51+
*
52+
* @param {int} fromIndex - The index to start sorting from. Default: 1.
53+
*
54+
* @param {int} toIndex - The index to stop sorting at. Default: 0.
55+
*/
56+
/proc/sortTim(list/to_sort, cmp = GLOBAL_PROC_REF(cmp_numeric_asc), associative = FALSE, fromIndex = 1, toIndex = 0) as /list
57+
CREATE_SORT_INSTANCE(to_sort, cmp, associative, fromIndex, toIndex)
58+
59+
sorter.timSort(fromIndex, toIndex)
60+
61+
return to_sort
62+
63+
64+
/**
65+
* ## Merge Sort
66+
* Divide and conquer sorting algorithm.
67+
*
68+
* @see
69+
* - https://en.wikipedia.org/wiki/Merge_sort
70+
*/
71+
/proc/sortMerge(list/to_sort, cmp = GLOBAL_PROC_REF(cmp_numeric_asc), associative = FALSE, fromIndex = 1, toIndex = 0) as /list
72+
CREATE_SORT_INSTANCE(to_sort, cmp, associative, fromIndex, toIndex)
73+
74+
sorter.mergeSort(fromIndex, toIndex)
75+
76+
return to_sort
77+
78+
79+
/**
80+
* ## Insertion Sort
81+
* Simple sorting algorithm that builds the final sorted list one item at a time.
82+
*
83+
84+
* @see
85+
* - https://en.wikipedia.org/wiki/Insertion_sort
86+
*/
87+
/proc/sortInsert(list/to_sort, cmp = GLOBAL_PROC_REF(cmp_numeric_asc), associative = FALSE, fromIndex = 1, toIndex = 0) as /list
88+
CREATE_SORT_INSTANCE(to_sort, cmp, associative, fromIndex, toIndex)
89+
90+
sorter.binarySort(fromIndex, toIndex)
91+
92+
return to_sort
93+
94+
95+
#undef CREATE_SORT_INSTANCE

code/__HELPERS/sorts/__main.dm renamed to code/__HELPERS/sorts/sort_instance.dm

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ GLOBAL_DATUM_INIT(sortInstance, /datum/sort_instance, new())
101101
if(start <= lo)
102102
start = lo + 1
103103

104-
for(,start < hi, ++start)
104+
var/list/L = src.L
105+
for(start in start to hi - 1)
105106
var/pivot = fetchElement(L,start)
106107

107108
//set left and right to the index where pivot belongs
@@ -140,6 +141,7 @@ GLOBAL_DATUM_INIT(sortInstance, /datum/sort_instance, new())
140141
if(runHi >= hi)
141142
return 1
142143

144+
var/list/L = src.L
143145
var/last = fetchElement(L,lo)
144146
var/current = fetchElement(L,runHi++)
145147

@@ -221,7 +223,6 @@ GLOBAL_DATUM_INIT(sortInstance, /datum/sort_instance, new())
221223
runLens.Cut(i+1, i+2)
222224
runBases.Cut(i+1, i+2)
223225

224-
225226
//Find where the first element of run2 goes in run1.
226227
//Prior elements in run1 can be ignored (because they're already in place)
227228
var/k = gallopRight(fetchElement(L,base2), base1, len1, 0)
@@ -259,6 +260,7 @@ GLOBAL_DATUM_INIT(sortInstance, /datum/sort_instance, new())
259260
/datum/sort_instance/proc/gallopLeft(key, base, len, hint)
260261
//ASSERT(len > 0 && hint >= 0 && hint < len)
261262

263+
var/list/L = src.L
262264
var/lastOffset = 0
263265
var/offset = 1
264266
if(call(cmp)(key, fetchElement(L,base+hint)) > 0)
@@ -318,6 +320,7 @@ GLOBAL_DATUM_INIT(sortInstance, /datum/sort_instance, new())
318320
/datum/sort_instance/proc/gallopRight(key, base, len, hint)
319321
//ASSERT(len > 0 && hint >= 0 && hint < len)
320322

323+
var/list/L = src.L
321324
var/offset = 1
322325
var/lastOffset = 0
323326
if(call(cmp)(key, fetchElement(L,base+hint)) < 0) //key <= L[base+hint]
@@ -366,6 +369,7 @@ GLOBAL_DATUM_INIT(sortInstance, /datum/sort_instance, new())
366369
/datum/sort_instance/proc/mergeLo(base1, len1, base2, len2)
367370
//ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2)
368371

372+
var/list/L = src.L
369373
var/cursor1 = base1
370374
var/cursor2 = base2
371375

@@ -468,6 +472,7 @@ GLOBAL_DATUM_INIT(sortInstance, /datum/sort_instance, new())
468472
/datum/sort_instance/proc/mergeHi(base1, len1, base2, len2)
469473
//ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2)
470474

475+
var/list/L = src.L
471476
var/cursor1 = base1 + len1 - 1 //start at end of sublists
472477
var/cursor2 = base2 + len2 - 1
473478

@@ -610,6 +615,7 @@ GLOBAL_DATUM_INIT(sortInstance, /datum/sort_instance, new())
610615
return L
611616

612617
/datum/sort_instance/proc/mergeAt2(i)
618+
var/list/L = src.L
613619
var/cursor1 = runBases[i]
614620
var/cursor2 = runBases[i+1]
615621

code/controllers/subsystem/dcs.dm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ PROCESSING_SUBSYSTEM_DEF(dcs)
5353
else
5454
fullid += REF(key)
5555

56-
if(length(named_arguments))
57-
named_arguments = sortTim(named_arguments, GLOBAL_PROC_REF(cmp_text_asc))
56+
if(named_arguments)
57+
sortTim(named_arguments, GLOBAL_PROC_REF(cmp_text_asc))
5858
fullid += named_arguments
5959

6060
return list2params(fullid)

code/datums/datum.dm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@
329329
ASSERT(isatom(src) || isimage(src))
330330
var/atom/atom_cast = src // filters only work with images or atoms.
331331
atom_cast.filters = null
332-
filter_data = sortTim(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE)
332+
sortTim(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE)
333333
for(var/filter_raw in filter_data)
334334
var/list/data = filter_data[filter_raw]
335335
var/list/arguments = data.Copy()

code/modules/admin/verbs/debug.dm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,7 @@
910910
var/list/sorted = list()
911911
for (var/source in per_source)
912912
sorted += list(list("source" = source, "count" = per_source[source]))
913-
sorted = sortTim(sorted, GLOBAL_PROC_REF(cmp_timer_data))
913+
sortTim(sorted, GLOBAL_PROC_REF(cmp_timer_data))
914914

915915
// Now that everything is sorted, compile them into an HTML output
916916
var/output = "<table border='1'>"

code/modules/antagonists/malf_ai/malf_ai_module_picker.dm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
filtered_modules[AM.category][AM] = AM
2525

2626
for(var/category in filtered_modules)
27-
filtered_modules[category] = sortTim(filtered_modules[category], GLOBAL_PROC_REF(cmp_malfmodules_priority))
27+
sortTim(filtered_modules[category], GLOBAL_PROC_REF(cmp_malfmodules_priority))
2828

2929
return filtered_modules
3030

code/modules/antagonists/pirate/pirate.dm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
//Lists notable loot.
8787
if(!cargo_hold || !cargo_hold.total_report)
8888
return "Nothing"
89-
cargo_hold.total_report.total_value = sortTim(cargo_hold.total_report.total_value, cmp = GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE)
89+
sortTim(cargo_hold.total_report.total_value, cmp = GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE)
9090
var/count = 0
9191
var/list/loot_texts = list()
9292
for(var/datum/export/E in cargo_hold.total_report.total_value)

code/modules/asset_cache/assets/uplink.dm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
var/list/items = list()
1010
for(var/datum/uplink_category/category as anything in subtypesof(/datum/uplink_category))
1111
categories += category
12-
categories = sortTim(categories, GLOBAL_PROC_REF(cmp_uplink_category_desc))
12+
sortTim(categories, GLOBAL_PROC_REF(cmp_uplink_category_desc))
1313

1414
var/list/new_categories = list()
1515
for(var/datum/uplink_category/category as anything in categories)

code/modules/modular_computers/file_system/programs/emojipedia.dm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
/datum/computer_file/program/emojipedia/New()
1515
. = ..()
1616
// Sort the emoji list so it's easier to find things and we don't have to keep sorting on ui_data since the number of emojis can not change in-game.
17-
emoji_list = sortTim(emoji_list, /proc/cmp_text_asc)
17+
sortTim(emoji_list, /proc/cmp_text_asc)
1818

1919
/datum/computer_file/program/emojipedia/ui_static_data(mob_user)
2020
var/list/data = list()

code/modules/research/stock_parts.dm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ If you create T5+ please take a pass at mech_fabricator.dm. The parts being good
228228
continue
229229
part_list += component_part
230230
//Sort the parts. This ensures that higher tier items are applied first.
231-
part_list = sortTim(part_list, GLOBAL_PROC_REF(cmp_rped_sort))
231+
sortTim(part_list, GLOBAL_PROC_REF(cmp_rped_sort))
232232
return part_list
233233

234234
/proc/cmp_rped_sort(obj/item/first_item, obj/item/second_item)

code/modules/unit_tests/unit_test.dm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
348348
if(length(focused_tests))
349349
tests_to_run = focused_tests
350350

351-
tests_to_run = sortTim(tests_to_run, GLOBAL_PROC_REF(cmp_unit_test_priority))
351+
sortTim(tests_to_run, GLOBAL_PROC_REF(cmp_unit_test_priority))
352352

353353
var/list/test_results = list()
354354

tgstation.dme

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -593,10 +593,8 @@
593593
#include "code\__HELPERS\paths\jps.dm"
594594
#include "code\__HELPERS\paths\path.dm"
595595
#include "code\__HELPERS\paths\sssp.dm"
596-
#include "code\__HELPERS\sorts\__main.dm"
597-
#include "code\__HELPERS\sorts\InsertSort.dm"
598-
#include "code\__HELPERS\sorts\MergeSort.dm"
599-
#include "code\__HELPERS\sorts\TimSort.dm"
596+
#include "code\__HELPERS\sorts\helpers.dm"
597+
#include "code\__HELPERS\sorts\sort_instance.dm"
600598
#include "code\__HELPERS\~monkestation-helpers\announcements.dm"
601599
#include "code\__HELPERS\~monkestation-helpers\antags.dm"
602600
#include "code\__HELPERS\~monkestation-helpers\atoms.dm"

0 commit comments

Comments
 (0)