Skip to content

Commit 6941ba7

Browse files
committed
[STJ] Only compute PopCount once when topologically sorting Enums
Packs the calculated (once per entry) PopCount into the high 32 bits of the long and the original index into the low 32 bits. Then that can just be sorted using the heavily optimized Array.Sort() method. After sorting just extract the low 32 bits as the original array index. As before, we negate the actual _PopCount_ to ensure that `Key`s with more on-bits (e.g. more flags represented) will sort **first**. This trades 2 x O(N log N) [average case] to 2 x O(N^2) [worst case] calls to the `popcount` instruction (or the emulation if NET is not defined) for N **shift-left-32** and **or** + N x **truncate to 32 bits**. It _also_ eliminates the overhead of the `CompareTo` method as it's now a direct `long` low-level compare.
1 parent 2fe0457 commit 6941ba7

File tree

1 file changed

+22
-23
lines changed
  • src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value

1 file changed

+22
-23
lines changed

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -676,44 +676,43 @@ private static EnumFieldInfo[] TopologicalSortEnumFields(EnumFieldInfo[] enumFie
676676
return enumFields;
677677
}
678678

679-
var indices = new int[enumFields.Length];
679+
var indices = new long[enumFields.Length];
680680
for (int i = 0; i < enumFields.Length; i++)
681681
{
682-
indices[i] = i;
682+
// we want values with more bits set to come first so negate the pop count
683+
int popCount = -PopCount(enumFields[i].Key);
684+
// pack into a long with the pop count in the high bits and the index in the low bits
685+
// this allows us to sort by pop count and then by index in case of ties
686+
indices[i] = ((long)popCount << 32) | (uint)i;
683687
}
684688

685-
Array.Sort(indices, (i, j) => GetComparisonKey(i).CompareTo(GetComparisonKey(j)));
689+
Array.Sort(indices);
686690

687691
var sortedFields = new EnumFieldInfo[enumFields.Length];
688692
for (int i = 0; i < indices.Length; i++)
689693
{
690-
sortedFields[i] = enumFields[indices[i]];
694+
// extract the index from the long, which is the low bits
695+
// the high bits are the pop count, which we don't need anymore
696+
int index = (int)(uint)indices[i];
697+
sortedFields[i] = enumFields[index];
691698
}
692699

693700
return sortedFields;
701+
}
694702

695-
(int PopCount, int Index) GetComparisonKey(int i)
696-
{
697-
// Sort by descending pop count of the enum value.
698-
// Since Array.Sort isn't a stable algorithm
699-
// append the current index to the comparison key.
700-
return (-PopCount(enumFields[i].Key), i);
701-
}
702-
703-
static int PopCount(ulong value)
704-
{
703+
private static int PopCount(ulong value)
704+
{
705705
#if NET
706-
return (int)ulong.PopCount(value);
706+
return (int)ulong.PopCount(value);
707707
#else
708-
int count = 0;
709-
while (value != 0)
710-
{
711-
value &= value - 1;
712-
count++;
713-
}
714-
return count;
715-
#endif
708+
int count = 0;
709+
while (value != 0)
710+
{
711+
value &= value - 1;
712+
count++;
716713
}
714+
return count;
715+
#endif
717716
}
718717

719718
private enum EnumFieldNameKind

0 commit comments

Comments
 (0)