Помимо производительности различных интегральных типов данных, я проверил производительность Int32 против Int64 (т. е. int против long) для реализации моего калькулятора простых чисел и обнаружил, что на моей машине x64 (Ryzen 1800X) заметной разницы не было.
Я не смог проверить с шортами (Int16 и UInt16), потому что они довольно быстро переполняются.
И, как отмечали другие, ваши короткие циклы запутывают ваши результаты и особенно ваши операторы отладки. Вместо этого вы должны попытаться использовать рабочий поток.
Вот сравнение производительности int и long:

Конечно, избегайте long (и всего, кроме простого int) для индексов массива, поскольку вы даже не можете их использовать, а приведение к int может только снизить производительность (неизмеримо в моем тесте).

Вот мой код профилирования, который опрашивает прогресс, поскольку рабочий поток вращается вечно. Он немного замедляется при повторных тестах, поэтому я обязательно протестировал в других порядках, а также по отдельности:
public static void Run() {
TestWrapper(new PrimeEnumeratorInt32());
TestWrapper(new PrimeEnumeratorInt64());
TestWrapper(new PrimeEnumeratorInt64Indices());
}
private static void TestWrapper<X>(X enumeration)
where X : IDisposable, IEnumerator {
int[] lapTimesMs = new int[] { 100, 300, 600, 1000, 3000, 5000, 10000 };
int sleepNumberBlockWidth = 2 + (int)Math.Ceiling(Math.Log10(lapTimesMs.Max()));
string resultStringFmt = string.Format("\tTotal time is {{0,-{0}}}ms, number of computed primes is {{1}}", sleepNumberBlockWidth);
int totalSlept = 0;
int offset = 0;
Stopwatch stopwatch = new Stopwatch();
Type t = enumeration.GetType();
FieldInfo field = t.GetField("_known", BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine("Testing {0}", t.Name);
_continue = true;
Thread thread = new Thread(InfiniteLooper);
thread.Start(enumeration);
stopwatch.Start();
foreach (int sleepSize in lapTimesMs) {
SleepExtensions.SleepWithProgress(sleepSize + offset);
//avoid race condition calling the Current property by using reflection to get private data
Console.WriteLine(resultStringFmt, stopwatch.ElapsedMilliseconds, ((IList)field.GetValue(enumeration)).Count);
totalSlept += sleepSize;
offset = totalSlept - (int)stopwatch.ElapsedMilliseconds;//synchronize to stopwatch laps
}
_continue = false;
thread.Join(100);//plz stop in time (Thread.Abort is no longer supported)
enumeration.Dispose();
stopwatch.Stop();
}
private static bool _continue = true;
private static void InfiniteLooper(object data) {
IEnumerator enumerator = (IEnumerator)data;
while (_continue && enumerator.MoveNext()) { }
}
}
Обратите внимание, что вы можете заменить SleepExtensions.SleepWithProgress только на Thread.Sleep
И три варианта профилируемого алгоритма:
Версия Int32
class PrimeEnumeratorInt32 : IEnumerator<int> {
public int Current { get { return this._known[this._currentIdx]; } }
object IEnumerator.Current { get { return this.Current; } }
private int _currentIdx = -1;
private List<int> _known = new List<int>() { 2, 3 };
public bool MoveNext() {
if (++this._currentIdx >= this._known.Count)
this._known.Add(this.ComputeNext(this._known[^1]));
return true;//no end
}
private int ComputeNext(int lastKnown) {
int current = lastKnown + 2;//start at 2 past last known value, which is guaranteed odd because we initialize up thru 3
int testIdx;
int sqrt;
bool isComposite;
while (true) {//keep going until a new prime is found
testIdx = 1;//all test values are odd, so skip testing the first known prime (two)
sqrt = (int)Math.Sqrt(current);//round down, and avoid casting due to the comparison type of the while loop condition
isComposite = false;
while (this._known[testIdx] <= sqrt) {
if (current % this._known[testIdx++] == 0L) {
isComposite = true;
break;
}
}
if (isComposite) {
current += 2;
} else {
return current;//and end
}
}
}
public void Reset() {
this._currentIdx = -1;
}
public void Dispose() {
this._known = null;
}
}
Версия Int64
class PrimeEnumeratorInt64 : IEnumerator<long> {
public long Current { get { return this._known[this._currentIdx]; } }
object IEnumerator.Current { get { return this.Current; } }
private int _currentIdx = -1;
private List<long> _known = new List<long>() { 2, 3 };
public bool MoveNext() {
if (++this._currentIdx >= this._known.Count)
this._known.Add(this.ComputeNext(this._known[^1]));
return true;//no end
}
private long ComputeNext(long lastKnown) {
long current = lastKnown + 2;//start at 2 past last known value, which is guaranteed odd because we initialize up thru 3
int testIdx;
long sqrt;
bool isComposite;
while (true) {//keep going until a new prime is found
testIdx = 1;//all test values are odd, so skip testing the first known prime (two)
sqrt = (long)Math.Sqrt(current);//round down, and avoid casting due to the comparison type of the while loop condition
isComposite = false;
while (this._known[testIdx] <= sqrt) {
if (current % this._known[testIdx++] == 0L) {
isComposite = true;
break;
}
}
if (isComposite)
current += 2;
else
return current;//and end
}
}
public void Reset() {
this._currentIdx = -1;
}
public void Dispose() {
this._known = null;
}
}
Int64 для значений и индексов
Обратите внимание на необходимое приведение индексов для доступа к списку _known.
class PrimeEnumeratorInt64Indices : IEnumerator<long> {
public long Current { get { return this._known[(int)this._currentIdx]; } }
object IEnumerator.Current { get { return this.Current; } }
private long _currentIdx = -1;
private List<long> _known = new List<long>() { 2, 3 };
public bool MoveNext() {
if (++this._currentIdx >= this._known.Count)
this._known.Add(this.ComputeNext(this._known[^1]));
return true;//no end
}
private long ComputeNext(long lastKnown) {
long current = lastKnown + 2;//start at 2 past last known value, which is guaranteed odd because we initialize up thru 3
long testIdx;
long sqrt;
bool isComposite;
while (true) {//keep going until a new prime is found
testIdx = 1;//all test values are odd, so skip testing the first known prime (two)
sqrt = (long)Math.Sqrt(current);//round down, and avoid casting due to the comparison type of the while loop condition
isComposite = false;
while (this._known[(int)testIdx] <= sqrt) {
if (current % this._known[(int)testIdx++] == 0L) {
isComposite = true;
break;
}
}
if (isComposite)
current += 2;
else
return current;//and end
}
}
public void Reset() {
this._currentIdx = -1;
}
public void Dispose() {
this._known = null;
}
}
Итого, моя тестовая программа использует 43 МБ памяти через 20 секунд для Int32 и 75 МБ памяти для Int64 из-за коллекции List<...> _known, что является самой большой разницей, которую я наблюдаю.
Я также профилировал версии, используя неподписанные типы. Вот мои результаты (режим выпуска):
Testing PrimeEnumeratorInt32
Total time is 20000 ms, number of computed primes is 3842603
Testing PrimeEnumeratorUInt32
Total time is 20001 ms, number of computed primes is 3841554
Testing PrimeEnumeratorInt64
Total time is 20001 ms, number of computed primes is 3839953
Testing PrimeEnumeratorUInt64
Total time is 20002 ms, number of computed primes is 3837199
Все 4 версии имеют практически одинаковую производительность. Я полагаю, что урок здесь состоит в том, чтобы никогда не предполагать, как это повлияет на производительность, и что вам следует вероятно использовать Int64, если вы ориентируетесь на архитектуру x64, поскольку она соответствует моей версии Int32 даже при увеличенном использовании памяти.
И проверка, что мой основной калькулятор работает:

P.S. В режиме выпуска были стабильные результаты, которые были на 1,1% быстрее.
П.П.С. Вот необходимые операторы using:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
person
Elaskanator
schedule
02.10.2020
byteимеет диапазон 0-255. Это не подписанный тип данных. - person Adam Robinson   schedule 07.04.2010DateTimeне подходит для низкоуровневого профилирования производительности. ИспользуйтеSystem.Diagnostics.Stopwatch. - person Adam Robinson   schedule 07.04.2010