agricultura - ¿Cómo obtener la marca de tiempo de la precisión de tics en.NET/C#?
agricultura de precision (10)
¡Todas estas sugerencias parecen demasiado difíciles! Si tiene Windows 8 o Server 2012 o superior, use GetSystemTimePreciseAsFileTime
siguiente manera:
[DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi)]
static extern void GetSystemTimePreciseAsFileTime(out long filetime);
public DateTimeOffset GetNow()
{
long fileTime;
GetSystemTimePreciseAsFileTime(out fileTime);
return DateTimeOffset.FromFileTime(fileTime);
}
Esto tiene mucha, mucha mejor precisión que DateTime.Now
sin ningún esfuerzo.
Consulte MSDN para obtener más información: http://msdn.microsoft.com/en-us/library/windows/desktop/hh706895(v=vs.85).aspx
Hasta ahora usaba DateTime.Now
para obtener marcas de tiempo, pero noté que si imprime DateTime.Now
en un bucle, verá que aumenta en saltos decrecimiento de aprox. 15 ms. Pero para ciertos escenarios en mi aplicación necesito obtener la marca de tiempo más precisa posible, preferiblemente con precisión tic (= 100 ns). ¿Algunas ideas?
Actualizar:
Aparentemente, QueryPerformanceCounter
/ QueryPerformanceCounter
es el camino a seguir, pero solo se puede usar para medir el tiempo, así que estaba pensando en llamar a DateTime.Now
cuando la aplicación se inicia y luego ejecutar StopWatch
y luego agregar el tiempo transcurrido desde StopWatch
al valor inicial devuelto por DateTime.Now
. Al menos eso debería darme marcas de tiempo relativas precisas, ¿verdad? ¿Qué piensas de eso (hack)?
NOTA:
StopWatch.ElapsedTicks
es diferente de StopWatch.Elapsed.Ticks
! Usé el primero suponiendo 1 tick = 100 ns, pero en este caso 1 tick = 1 / StopWatch.Frequency
. Entonces, para obtener marcas equivalentes a DateTime, use StopWatch.Elapsed.Ticks
. Acabo de aprender esto de la manera difícil.
NOTA 2:
Usando el enfoque de cronómetro, noté que no se sincronizaba con el tiempo real. Después de aproximadamente 10 horas, estaba adelantado por 5 segundos. Así que supongo que uno tendría que volver a sincronizar cada X o algo así, donde X podría ser de 1 hora, 30 minutos, 15 minutos, etc. No estoy seguro de cuál sería el intervalo de tiempo óptimo para la resincronización, ya que cada resincronización cambiará el offset que puede ser hasta 20 ms.
1). Si necesita una precisión absoluta de alta resolución: no puede usar DateTime.Now
cuando se basa en un reloj con un intervalo de 15 ms (a menos que sea posible "deslizar" la fase).
En cambio, una fuente externa de mejor resolución, el tiempo de precisión absoluta (por ejemplo, ntp), t1
continuación, podría combinarse con el temporizador de alta resolución (cronómetro / QueryPerformanceCounter).
2). Si solo necesitas alta resolución:
Muestra DateTime.Now
( t1
) una vez junto con un valor del temporizador de alta resolución (StopWatch / QueryPerformanceCounter) ( tt0
).
Si el valor actual del temporizador de alta resolución es tt
entonces la hora actual, t
, es:
t = t1 + (tt - tt0)
3). Una alternativa podría ser separar el tiempo absoluto y el orden de los eventos financieros: un valor para el tiempo absoluto (resolución de 15 ms, posiblemente desactivado por varios minutos) y un valor para el pedido (por ejemplo, incrementar un valor por uno cada vez y almacenar ese). El valor de inicio para la orden puede basarse en algún valor global del sistema o derivarse del tiempo absoluto cuando se inicia la aplicación.
Esta solución sería más robusta ya que no depende de la implementación del hardware subyacente de los relojes / temporizadores (que pueden variar entre los sistemas).
Devuelve la fecha y hora más precisas que conoce el sistema operativo.
El sistema operativo también proporciona un tiempo de resolución más alto a través de QueryPerformanceCounter
y QueryPerformanceFrequency
(clase .NET Stopwatch
). Estos le permiten cronometrar un intervalo pero no le dan la fecha y la hora del día. Podría argumentar que estos podrían darle una hora y día muy precisos, pero no estoy seguro de cuánto se tuercen durante un largo intervalo.
El valor del reloj del sistema que DateTime.Now
lee solo se actualiza cada 15 ms aproximadamente (o 10 ms en algunos sistemas), por lo que los tiempos se cuantifican en esos intervalos. Existe un efecto de cuantificación adicional que resulta del hecho de que su código se está ejecutando en un sistema operativo multiproceso y, por lo tanto, existen tramos donde su aplicación no está "activa" y, por lo tanto, no mide la hora actual.
Ya que está buscando un valor de marca de tiempo ultra preciso (en lugar de limitar el tiempo de una duración arbitraria), la clase Stopwatch
por sí sola no hará lo que necesita. Creo que tendrías que hacer esto tú mismo con una especie de híbrido DateTime
/ Stopwatch
. Cuando se inicia la aplicación, debe almacenar el valor actual de DateTime.UtcNow
(es decir, el tiempo de resolución cruda cuando se inicia la aplicación) y luego también iniciar un objeto de Stopwatch
, como este:
DateTime _starttime = DateTime.UtcNow;
Stopwatch _stopwatch = Stopwatch.StartNew();
Entonces, cada vez que necesite un valor DateTime
alta resolución, lo obtendría así:
DateTime highresDT = _starttime.AddTicks(_stopwatch.Elapsed.Ticks);
También es posible que desee restablecer periódicamente _starttime y _stopwatch, para evitar que el tiempo resultante se desincronice demasiado con la hora del sistema (aunque no estoy seguro de que esto realmente suceda, y de todos modos tomaría mucho tiempo) .
Actualización : dado que parece que Cronómetro no se sincroniza con la hora del sistema (hasta medio segundo por hora), creo que tiene sentido restablecer la clase híbrida DateTime
función de la cantidad de tiempo que transcurre entre las llamadas a mira la hora:
public class HiResDateTime
{
private static DateTime _startTime;
private static Stopwatch _stopWatch = null;
private static TimeSpan _maxIdle =
TimeSpan.FromSeconds(10);
public static DateTime UtcNow
{
get
{
if ((_stopWatch == null) ||
(_startTime.Add(_maxIdle) < DateTime.UtcNow))
{
Reset();
}
return _startTime.AddTicks(_stopWatch.Elapsed.Ticks);
}
}
private static void Reset()
{
_startTime = DateTime.UtcNow;
_stopWatch = Stopwatch.StartNew();
}
}
Si restablece el temporizador híbrido a intervalos regulares (por ejemplo, cada hora o algo así), corre el riesgo de ajustar el tiempo antes de la última hora de lectura, como un problema en miniatura del horario de verano.
Esto es demasiado trabajo para algo tan simple. Simplemente inserte un DateTime en su base de datos con la operación. Luego, para obtener orden comercial, use su columna de identidad, que debería ser un valor creciente.
Si está insertando en múltiples bases de datos y tratando de reconciliarse después del hecho, estará calculando erróneamente la orden comercial debido a cualquier pequeña variación en los tiempos de su base de datos (incluso ns incrementos como usted dijo)
Para resolver el problema de la base de datos múltiple, podría exponer un solo servicio que cada operación realiza para obtener un pedido. El servicio podría devolver un DateTime.UtcNow.Ticks y un número comercial en aumento.
Incluso utilizando una de las soluciones anteriores, cualquier persona que realice operaciones desde una ubicación en la red con más latencia posiblemente coloque las transacciones en primer lugar (mundo real), pero se ingresan en el orden incorrecto debido a la latencia. Debido a esto, la operación debe considerarse ubicada en la base de datos, no en las consolas de los usuarios.
La precisión de 15 ms (en realidad puede ser de 15-25 ms) se basa en la resolución del temporizador de Windows 55 Hz / 65 Hz. Este es también el período básico de TimeSlice. Los afectados son Windows.Forms.Timer , Threading.Thread.Sleep , Threading.Timer , etc.
Para obtener intervalos precisos, debe usar la clase Cronómetro . Utilizará alta resolución si está disponible. Pruebe las siguientes declaraciones para averiguar:
Console.WriteLine("H = {0}", System.Diagnostics.Stopwatch.IsHighResolution);
Console.WriteLine("F = {0}", System.Diagnostics.Stopwatch.Frequency);
Console.WriteLine("R = {0}", 1.0 /System.Diagnostics.Stopwatch.Frequency);
Obtengo R = 6E-08 seg, o 60 ns. Debería ser suficiente para su propósito.
Para obtener un conteo de ticks de alta resolución, use el método estático Stopwatch.GetTimestamp ():
long tickCount = System.Diagnostics.Stopwatch.GetTimestamp();
DateTime highResDateTime = new DateTime(tickCount);
solo eche un vistazo al Código fuente de .NET:
public static long GetTimestamp() {
if(IsHighResolution) {
long timestamp = 0;
SafeNativeMethods.QueryPerformanceCounter(out timestamp);
return timestamp;
}
else {
return DateTime.UtcNow.Ticks;
}
}
Código fuente aquí: http://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Stopwatch.cs,69c6c3137e12dab4
Si necesita la marca de tiempo para realizar pruebas comparativas, use el cronómetro que tiene una precisión mucho mejor que DateTime.Now.
[La respuesta aceptada no parece ser segura para subprocesos y, según su propia admisión, puede retroceder en el tiempo provocando marcas de tiempo duplicadas, de ahí esta respuesta alternativa]
Si lo que realmente te importa (según tu comentario) es, de hecho, una marca de tiempo única asignada en estricto orden ascendente y que se corresponde lo más posible con la hora del sistema, puedes probar este enfoque alternativo:
public class HiResDateTime
{
private static long lastTimeStamp = DateTime.UtcNow.Ticks;
public static long UtcNowTicks
{
get
{
long orig, newval;
do
{
orig = lastTimeStamp;
long now = DateTime.UtcNow.Ticks;
newval = Math.Max(now, orig + 1);
} while (Interlocked.CompareExchange
(ref lastTimeStamp, newval, orig) != orig);
return newval;
}
}
}
Agregaría lo siguiente con respecto a MusiGenesis Answer para el tiempo de resincronización .
Significado: ¿A qué hora debo usar para volver a sincronizar (la _maxIdle
en las respuestas de MusiGenesis )
Sabes que con esta solución no eres perfectamente preciso, por eso te vuelves a sincronizar. Pero también lo que implícitamente quieres es lo mismo que la solución de Ian Mercer :
una marca de tiempo única que se asigna en estricto orden ascendente
Por lo tanto, la cantidad de tiempo entre dos re-sync ( _maxIdle
Lets lo llama SyncTime ) debe ser función de 4 cosas:
- la resolución
DateTime.UtcNow
- la relación de precisión que desea
- el nivel de precisión que deseas
- Una estimación de la proporción fuera de sincronización de su máquina
Obviamente, la primera limitación en esta variable sería:
relación fuera de sincronización <= relación de precisión
Por ejemplo: no quiero que mi precisión sea peor que 0.5s / hrs o 1ms / day, etc. ... (en mal inglés: no quiero estar más equivocado que 0.5s / hrs = 12s / day).
Por lo tanto, no puede lograr una precisión superior a la que le ofrece el cronómetro en su PC. Depende de su proporción fuera de sincronización, que puede no ser constante.
Otra restricción es el tiempo mínimo entre dos resincronizaciones:
Synctime> = DateTime.UtcNow
resolución
Aquí la precisión y la precisión están vinculadas porque si usa una precisión alta (por ejemplo, para almacenar en un DB) pero con una precisión menor, puede romper la declaración de Ian Mercer que es el orden ascendente estricto.
Nota: Parece que DateTime.UtcNow puede tener un res predeterminado mayor que 15ms (1ms en mi máquina) Siga el enlace: Alta precisión DateTime.UtcNow
Tomemos un ejemplo:
Imagine la proporción fuera de sincronización comentada anteriormente.
Después de aproximadamente 10 horas, estaba adelantado por 5 segundos.
Digamos que quiero una precisión microsec. Mi res temporizador es 1 ms (ver Nota anterior)
Entonces punto por punto:
- la resolución
DateTime.UtcNow
: 1 ms - relación de precisión> = relación fuera de sincronización, permite tomar la más precisa posible así: relación de precisión = relación fuera de sincronización
- el nivel de precisión que desea: 1 microsec
- Una estimación de la relación fuera de sincronización de su máquina: 0.5s / hour (esta es también mi precisión)
Si restablece cada 10 segundos, imagínese que está en 9.999s, 1ms antes del reinicio. Aquí haces una llamada durante este intervalo. El tiempo que trazará su función está adelantado por: 0.5 / 3600 * 9.999s eq 1.39ms. Usted mostraría un tiempo de 10.000390 segundos. Después del tic de UtcNow, si haces una llamada dentro del 390micro sec, tendrás un número inferior al anterior. Es peor si esta relación fuera de sincronización es aleatoria dependiendo de la carga de la CPU u otras cosas.
Ahora digamos que puse SyncTime en su valor mínimo> I resync cada 1ms
Hacer el mismo pensamiento me pondría por delante en 0.139 microsec <inferior a la precisión que quiero. Por lo tanto, si llamo a la función a 9.999 ms, entonces 1microsec antes del reinicio, trazaré 9.999. Y justo después de trazaré 10.000. Tendré un buen orden.
Así que aquí la otra restricción es: ratio de precisión x SyncTime <nivel de precisión, digamos que para estar seguro porque el número se puede redondear a esa relación de precisión x SyncTime <nivel de precisión / 2 es bueno.
El problema está resuelto
Entonces, una recapitulación rápida sería:
- Recupera tu resolución de temporizador.
- Calcule una estimación de la proporción fuera de sincronización.
- relación de precisión> = estimación de proporción fuera de sincronización, Mejor precisión = relación fuera de sincronización
- Elija su nivel de precisión teniendo en cuenta lo siguiente:
- timer-resolution <= SyncTime <= PrecisionLevel / (2 * accuracy-ratio)
- La mejor precisión que puede lograr es la relación de resolución de temporizador * 2 * fuera de sincronización
Para la relación anterior (0.5 / h), el SyncTime correcto sería de 3.6ms, por lo que se redondearía a 3ms .
Con la relación anterior y la resolución del temporizador de 1 ms. Si desea un nivel de precisión de una marca (0.1microsec), necesita una proporción fuera de sincronización de no más de: 180ms / hora.
En su última respuesta a su propia respuesta, el estado MusiGenesis :
@Hermann: He estado ejecutando una prueba similar durante las últimas dos horas (sin la corrección de reinicio), y el temporizador basado en el cronómetro solo está funcionando unos 400 ms adelante después de 2 horas, por lo que el sesgo en sí parece ser variable (pero sigue siendo bastante severo). Estoy bastante sorprendido de que el sesgo sea tan malo; Supongo que esta es la razón por la que Stopwatch está en System.Diagnostics. - MusiGenesis
Por lo tanto, la precisión del Cronómetro es cercana a 200ms / hora, casi nuestra 180ms / hora. ¿Hay algún vínculo con por qué nuestro número y este número están tan cerca? No lo se. Pero esta precisión es suficiente para que logremos Tick-Precision
El mejor nivel de precisión: para el ejemplo anterior, es 0.27 microsec.
Sin embargo, qué sucede si lo llamo varias veces entre 9.999ms y la resincronización.
2 llamadas a la función podrían terminar con el mismo TimeStamp que se devuelve, el tiempo sería 9.999 para ambos (ya que no veo más precisión). Para evitar esto, no puede tocar el nivel de precisión porque está Vinculado a SyncTime por la relación anterior. Entonces debería implementar la solución de Ian Mercer para esos casos.
Por favor, no dudes en comentar mi respuesta.