Obliczenia zmiennoprzecinkowe vs integer na nowoczesnym sprzęcie

Wykonuję pewną krytyczną pracę w C++ i obecnie używamy obliczeń całkowitych dla problemów, które są z natury zmiennoprzecinkowe, ponieważ "są szybsze". Powoduje to wiele irytujących problemów i dodaje wiele irytującego kodu.

Teraz, pamiętam jak czytałem o tym, jak obliczenia zmiennoprzecinkowe były tak powolne około około 386 dni, gdzie wierzę (IIRC), że był opcjonalny współprocesor. Ale z pewnością w dzisiejszych czasach z wykładniczo bardziej złożonymi i potężne procesory nie ma różnicy w "prędkości", jeśli wykonujesz obliczenia zmiennoprzecinkowe lub całkowite? Zwłaszcza, że rzeczywisty czas obliczeń jest mały w porównaniu do czegoś takiego jak spowodowanie wstrzymania rurociągu lub pobranie czegoś z pamięci głównej?

Wiem, że poprawną odpowiedzią jest testowanie na docelowym sprzęcie, jaki byłby dobry sposób, aby to przetestować? Napisałem dwa malutkie programy C++ i porównałem ich czas wykonania z "time" na Linuksie, ale rzeczywisty czas wykonania jest zbyt zmienny (nie pomaga mi działa na serwerze wirtualnym). Bez spędzania całego dnia na uruchamianiu setek benchmarków, robieniu wykresów itp. czy jest coś, co mogę zrobić, aby uzyskać rozsądny test prędkości względnej? Jakieś pomysły lub przemyślenia? Całkowicie się mylę?

Programy, których używałem w następujący sposób, nie są identyczne w żaden sposób:

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{
    int accum = 0;

    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += rand( ) % 365;
    }
    std::cout << accum << std::endl;

    return 0;
}

Program 2:

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{

    float accum = 0;
    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += (float)( rand( ) % 365 );
    }
    std::cout << accum << std::endl;

    return 0;
}
Z góry dzięki!

Edit: platforma, na której mi zależy, to zwykły x86 lub x86-64 działający na pulpicie Linux i Windows maszyny.

Edit 2 (wklejony z komentarza poniżej): mamy obecnie rozbudowaną bazę kodu. Naprawdę mam pochodzić przeciwko uogólnieniu, że "nie wolno używać float, ponieważ obliczenia integer jest szybsze" - i szukam sposobu (jeśli jest to w ogóle prawda), aby obalić to uogólnione założenie. Zdaję sobie sprawę, że nie da się przewidzieć dokładnego wyniku bez wykonywania całej pracy i profilowania jej później.

W każdym razie, dzięki za wszystkie doskonałe odpowiedzi i pomocy. Zapraszam do dodawania czegokolwiek innego :).

Author: mskfisher, 2010-03-31

11 answers

Niestety, mogę tylko dać ci odpowiedź "to zależy"...

Z mojego doświadczenia wynika, że wydajność jest bardzo duża...zwłaszcza między liczbami całkowitymi i zmiennoprzecinkowymi. Różni się znacznie w zależności od procesora (nawet w obrębie tej samej rodziny, jak np. x86), ponieważ różne procesory mają różne długości "rurociągów". Ponadto niektóre operacje są na ogół bardzo proste (np. dodawanie) i mają przyspieszoną drogę przez procesor, a inne (np. podział) przyjmują dużo, dużo dłużej.

Druga duża zmienna jest miejscem, w którym znajdują się dane. Jeśli masz tylko kilka wartości do dodania, wszystkie dane mogą znajdować się w pamięci podręcznej, gdzie można je szybko wysłać do procesora. Bardzo, bardzo wolna operacja zmiennoprzecinkowa, która ma już dane w pamięci podręcznej, będzie wielokrotnie szybsza niż operacja integer, w której liczba całkowita musi być skopiowana z pamięci systemowej.

Zakładam, że zadajesz to pytanie, ponieważ pracujesz nad wydajnością krytyczną podanie. Jeśli pracujesz nad architekturą x86 i potrzebujesz dodatkowej wydajności, warto rozważyć użycie rozszerzeń SSE. Może to znacznie przyspieszyć arytmetykę zmiennoprzecinkową z pojedynczą precyzją, ponieważ ta sama operacja może być wykonana na wielu danych jednocześnie, plus istnieje oddzielny * bank rejestrów dla operacji SSE. (Zauważyłem, że w drugim przykładzie użyłeś "float" zamiast "double", co sprawia, że myślę, że używasz matematyki z pojedynczą precyzją).

*uwaga: Używanie starych instrukcji MMX faktycznie spowolniłoby programy, ponieważ te stare instrukcje faktycznie używały tych samych rejestrów co FPU, co uniemożliwiało używanie zarówno FPU, jak i MMX w tym samym czasie.

 31
Author: Dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-19 21:03:42

Na przykład (mniejsze liczby są szybsze),

64-bit Intel Xeon X5550 @ 2.67 GHz, gcc 4.1.2 -O3

short add/sub: 1.005460 [0]
short mul/div: 3.926543 [0]
long add/sub: 0.000000 [0]
long mul/div: 7.378581 [0]
long long add/sub: 0.000000 [0]
long long mul/div: 7.378593 [0]
float add/sub: 0.993583 [0]
float mul/div: 1.821565 [0]
double add/sub: 0.993884 [0]
double mul/div: 1.988664 [0]

32-bitowy Dwurdzeniowy Procesor AMD Opteron(tm) 265 @ 1.81 GHz, gcc 3.4.6 -O3

short add/sub: 0.553863 [0]
short mul/div: 12.509163 [0]
long add/sub: 0.556912 [0]
long mul/div: 12.748019 [0]
long long add/sub: 5.298999 [0]
long long mul/div: 20.461186 [0]
float add/sub: 2.688253 [0]
float mul/div: 4.683886 [0]
double add/sub: 2.700834 [0]
double mul/div: 4.646755 [0]

Jak zauważył Dan , nawet po normalizacji częstotliwości zegara (co może być mylące samo w sobie w projektach pipelinowanych), wyniki będą się bardzo różnić w zależności od architektury procesora (Indywidualne ALU/FPU wydajność, jako podobnie jak rzeczywista liczba Alu/FPUdostępna na rdzeń w superscalarze, która wpływa na to, ile niezależnych operacji może wykonać równolegle -- ten ostatni czynnik nie jest wykonywany przez poniższy kod, ponieważ wszystkie poniższe operacje są kolejno zależne.)

Poor man ' s FPU/ALU Operation benchmark:

#include <stdio.h>
#ifdef _WIN32
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif
#include <time.h>
#include <cstdlib>

double
mygettime(void) {
# ifdef _WIN32
  struct _timeb tb;
  _ftime(&tb);
  return (double)tb.time + (0.001 * (double)tb.millitm);
# else
  struct timeval tv;
  if(gettimeofday(&tv, 0) < 0) {
    perror("oops");
  }
  return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
# endif
}

template< typename Type >
void my_test(const char* name) {
  Type v  = 0;
  // Do not use constants or repeating values
  //  to avoid loop unroll optimizations.
  // All values >0 to avoid division by 0
  // Perform ten ops/iteration to reduce
  //  impact of ++i below on measurements
  Type v0 = (Type)(rand() % 256)/16 + 1;
  Type v1 = (Type)(rand() % 256)/16 + 1;
  Type v2 = (Type)(rand() % 256)/16 + 1;
  Type v3 = (Type)(rand() % 256)/16 + 1;
  Type v4 = (Type)(rand() % 256)/16 + 1;
  Type v5 = (Type)(rand() % 256)/16 + 1;
  Type v6 = (Type)(rand() % 256)/16 + 1;
  Type v7 = (Type)(rand() % 256)/16 + 1;
  Type v8 = (Type)(rand() % 256)/16 + 1;
  Type v9 = (Type)(rand() % 256)/16 + 1;

  double t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v += v0;
    v -= v1;
    v += v2;
    v -= v3;
    v += v4;
    v -= v5;
    v += v6;
    v -= v7;
    v += v8;
    v -= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s add/sub: %f [%d]\n", name, mygettime() - t1, (int)v&1);
  t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v /= v0;
    v *= v1;
    v /= v2;
    v *= v3;
    v /= v4;
    v *= v5;
    v /= v6;
    v *= v7;
    v /= v8;
    v *= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s mul/div: %f [%d]\n", name, mygettime() - t1, (int)v&1);
}

int main() {
  my_test< short >("short");
  my_test< long >("long");
  my_test< long long >("long long");
  my_test< float >("float");
  my_test< double >("double");

  return 0;
}
 47
Author: vladr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-25 08:41:45

Istnieje prawdopodobnie znacząca różnica w rzeczywistej prędkości między matematyką stacjonarną i zmiennoprzecinkową, ale teoretyczna przepustowość ALU vs FPU jest całkowicie nieistotna. Zamiast tego, Liczba rejestrów całkowitych i zmiennoprzecinkowych (rejestrów rzeczywistych, a nie nazw rejestrów) na Twojej architekturze, które nie są używane w innych obliczeniach (np. do sterowania pętlą), liczba elementów każdego typu, które mieszczą się w linii pamięci podręcznej, możliwe optymalizacje biorąc pod uwagę różne semantyki dla liczb całkowitych vs. zmiennoprzecinkowych matematyki -- te efekty będą dominować. Zależności danych Twojego algorytmu odgrywają tutaj znaczącą rolę, więc żadne ogólne porównanie nie przewidzi luki w wydajności Twojego problemu.

Na przykład, dodawanie liczb całkowitych jest przemienne, więc jeśli kompilator widzi pętlę taką jak ta użyta w benchmarku (zakładając, że losowe DANE zostały przygotowane wcześniej, aby nie przesłaniały wyników), może rozwinąć pętlę i obliczyć sumy częściowe za pomocą bez zależności, a następnie dodać je po zakończeniu pętli. Ale w przypadku zmiennoprzecinkowych, kompilator musi wykonywać operacje w tej samej kolejności, o którą prosiłeś(masz tam punkty sekwencji, więc kompilator musi zagwarantować ten sam wynik, co uniemożliwia zmianę kolejności), więc istnieje silna zależność każdego dodawania od wyniku poprzedniego.

Prawdopodobnie zmieścisz więcej liczb całkowitych w pamięci podręcznej na raz. Tak więc wersja ze stałym punktem może przewyższyć wersję float o rząd wielkości nawet na maszynie, w której FPU ma teoretycznie wyższą przepustowość.

 18
Author: Ben Voigt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-31 05:20:48

Dodawanie jest znacznie szybsze niż rand, więc twój program jest (szczególnie) bezużyteczny.

Musisz zidentyfikować hotspoty wydajności i stopniowo modyfikować swój program. Wygląda na to, że masz problemy ze środowiskiem programistycznym, które trzeba najpierw rozwiązać. Czy niemożliwe jest uruchomienie programu na komputerze dla małego zestawu problemów?

Ogólnie rzecz biorąc, próba zadania FP z arytmetyką całkowitą jest przepisem na powolne.

 17
Author: Potatoswatter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-31 03:24:05

Dopóki to się nie zmieni (dużo). Oto kilka wyników przy użyciu kompilatora gnu (btw sprawdzałem również kompilując na maszynach, gnu g++ 5.4 od xeniala jest o wiele szybszy niż 4.6.3 od linaro na precise)

Intel i7 4700MQ xenial

short add: 0.822491
short sub: 0.832757
short mul: 1.007533
short div: 3.459642
long add: 0.824088
long sub: 0.867495
long mul: 1.017164
long div: 5.662498
long long add: 0.873705
long long sub: 0.873177
long long mul: 1.019648
long long div: 5.657374
float add: 1.137084
float sub: 1.140690
float mul: 1.410767
float div: 2.093982
double add: 1.139156
double sub: 1.146221
double mul: 1.405541
double div: 2.093173

Intel i3 2370M ma podobne wyniki

short add: 1.369983
short sub: 1.235122
short mul: 1.345993
short div: 4.198790
long add: 1.224552
long sub: 1.223314
long mul: 1.346309
long div: 7.275912
long long add: 1.235526
long long sub: 1.223865
long long mul: 1.346409
long long div: 7.271491
float add: 1.507352
float sub: 1.506573
float mul: 2.006751
float div: 2.762262
double add: 1.507561
double sub: 1.506817
double mul: 1.843164
double div: 2.877484

Intel (R) Celeron (R )2955U (Acer C720 Chromebook z systemem xenial)

short add: 1.999639
short sub: 1.919501
short mul: 2.292759
short div: 7.801453
long add: 1.987842
long sub: 1.933746
long mul: 2.292715
long div: 12.797286
long long add: 1.920429
long long sub: 1.987339
long long mul: 2.292952
long long div: 12.795385
float add: 2.580141
float sub: 2.579344
float mul: 3.152459
float div: 4.716983
double add: 2.579279
double sub: 2.579290
double mul: 3.152649
double div: 4.691226

DigitalOcean 1GB Droplet intel(R) Xeon(R) CPU E5-2630L v2 (running trusty)

short add: 1.094323
short sub: 1.095886
short mul: 1.356369
short div: 4.256722
long add: 1.111328
long sub: 1.079420
long mul: 1.356105
long div: 7.422517
long long add: 1.057854
long long sub: 1.099414
long long mul: 1.368913
long long div: 7.424180
float add: 1.516550
float sub: 1.544005
float mul: 1.879592
float div: 2.798318
double add: 1.534624
double sub: 1.533405
double mul: 1.866442
double div: 2.777649

AMD Procesor Opteron (tm) 4122 (precyzyjny)

short add: 3.396932
short sub: 3.530665
short mul: 3.524118
short div: 15.226630
long add: 3.522978
long sub: 3.439746
long mul: 5.051004
long div: 15.125845
long long add: 4.008773
long long sub: 4.138124
long long mul: 5.090263
long long div: 14.769520
float add: 6.357209
float sub: 6.393084
float mul: 6.303037
float div: 17.541792
double add: 6.415921
double sub: 6.342832
double mul: 6.321899
double div: 15.362536

To używa kodu z http://pastebin.com/Kx8WGUfg jako benchmark-pc.c

g++ -fpermissive -O3 -o benchmark-pc benchmark-pc.c

Przeprowadziłem wiele podań, ale wygląda na to, że liczby ogólne są takie same.

Jeden zauważalny wyjątek wydaje się być Alu mul vs FPU mul. Dodawanie i odejmowanie wydają się trywialnie różne.

Oto powyższy w formie wykresu (kliknij na pełny rozmiar, niżej jest szybciej i "preferent"): {]}

Wykres powyższych danych

Update to accommodate @Peter Cordes

Https://gist.github.com/Lewiscowles1986/90191c59c9aedf3d08bf0b129065cccc

i7 4700MQ Linux Ubuntu xenial 64-bit (wszystkie poprawki do 2018-03-13 zastosowane)
    short add: 0.773049
    short sub: 0.789793
    short mul: 0.960152
    short div: 3.273668
      int add: 0.837695
      int sub: 0.804066
      int mul: 0.960840
      int div: 3.281113
     long add: 0.829946
     long sub: 0.829168
     long mul: 0.960717
     long div: 5.363420
long long add: 0.828654
long long sub: 0.805897
long long mul: 0.964164
long long div: 5.359342
    float add: 1.081649
    float sub: 1.080351
    float mul: 1.323401
    float div: 1.984582
   double add: 1.081079
   double sub: 1.082572
   double mul: 1.323857
   double div: 1.968488
Procesor AMD Opteron (tm) 4122 (precyzyjny, DreamHost shared-hosting)
    short add: 1.235603
    short sub: 1.235017
    short mul: 1.280661
    short div: 5.535520
      int add: 1.233110
      int sub: 1.232561
      int mul: 1.280593
      int div: 5.350998
     long add: 1.281022
     long sub: 1.251045
     long mul: 1.834241
     long div: 5.350325
long long add: 1.279738
long long sub: 1.249189
long long mul: 1.841852
long long div: 5.351960
    float add: 2.307852
    float sub: 2.305122
    float mul: 2.298346
    float div: 4.833562
   double add: 2.305454
   double sub: 2.307195
   double mul: 2.302797
   double div: 5.485736
Intel Xeon E5-2630L v2 @ 2.4 GHz (zaufany 64-bit, DigitalOcean VPS)
    short add: 1.040745
    short sub: 0.998255
    short mul: 1.240751
    short div: 3.900671
      int add: 1.054430
      int sub: 1.000328
      int mul: 1.250496
      int div: 3.904415
     long add: 0.995786
     long sub: 1.021743
     long mul: 1.335557
     long div: 7.693886
long long add: 1.139643
long long sub: 1.103039
long long mul: 1.409939
long long div: 7.652080
    float add: 1.572640
    float sub: 1.532714
    float mul: 1.864489
    float div: 2.825330
   double add: 1.535827
   double sub: 1.535055
   double mul: 1.881584
   double div: 2.777245
 11
Author: MrMesees,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-13 18:08:21

Dwa punkty do rozważenia -

Nowoczesny sprzęt może nakładać instrukcje, wykonywać je równolegle i zmieniać ich kolejność, aby jak najlepiej wykorzystać sprzęt. Ponadto, każdy znaczący program zmiennoprzecinkowy może mieć również znaczącą pracę całkowitą, nawet jeśli oblicza tylko indeksy na tablice, licznik pętli itp. więc nawet jeśli masz wolną instrukcję zmiennoprzecinkową, może ona być uruchomiona na oddzielnym bitie sprzętowym nakładającym się na Część pracy całkowitej. Chodzi mi o to, że nawet jeśli instrukcje zmiennoprzecinkowe są powolne niż te całkowite, ogólny program może działać szybciej, ponieważ może wykorzystać więcej sprzętu.

Jak zawsze, jedynym sposobem na upewnienie się jest profilowanie rzeczywistego programu.

Druga kwestia polega na tym, że większość procesorów w dzisiejszych czasach ma instrukcje SIMD dla zmiennoprzecinkowych, które mogą działać na wielu wartościach zmiennoprzecinkowych w tym samym czasie. Na przykład można załadować 4 pływaki do jednego rejestru SSE i wykonać 4 mnożenia na nich wszystkich równolegle. Jeśli możesz przepisać części kodu, aby używać instrukcji SSE, to wydaje się, że będzie to szybsze niż wersja integer. Visual c++ dostarcza wbudowane funkcje kompilatora, zobacz http://msdn.microsoft.com/en-us/library/x5c07e2a(V = VS. 80). aspx dla niektórych informacji.

 7
Author: jcoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-31 08:11:44

Przeprowadziłem test, który właśnie dodał 1 do liczby zamiast rand (). Wyniki (na x86-64) były następujące:

  • krótki: 4.260 s
  • int: 4.020 s
  • long long: 3.350 s
  • float: 7.330 s
  • double: 7.210 s
 3
Author: dan04,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-31 04:47:24

O ile nie piszesz kodu, który będzie wywoływany miliony razy na sekundę (np. rysowanie linii na ekran w aplikacji graficznej), wąskim gardłem rzadko jest liczba całkowita a arytmetyka zmiennoprzecinkowa.

Zwykle pierwszym krokiem do pytań dotyczących wydajności jest profilowanie kodu, aby zobaczyć, gdzie naprawdę spędzony jest czas. Polecenie linuksowe to gprof.

Edit:

Chociaż przypuszczam, że zawsze można zaimplementować algorytm rysowania linii za pomocą liczby całkowite i zmiennoprzecinkowe, nazwij to dużą liczbą razy i sprawdź, czy to robi różnicę:

Http://en.wikipedia.org/wiki/Bresenham " s_algorithm

 3
Author: Artem Sokolov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-31 05:05:50

Wersja zmiennoprzecinkowa będzie znacznie wolniejsza, jeśli nie ma operacji pozostałej. Ponieważ wszystkie dodawania są sekwencyjne, procesor nie będzie w stanie wykonać równoległego podsumowania. Opóźnienie będzie krytyczne. Opóźnienie dodawania FPU wynosi zwykle 3 cykle, podczas gdy integer add to 1 cykl. Jednak dzielnik dla operatora reszty będzie prawdopodobnie częścią krytyczną, ponieważ nie jest w pełni pipelinowany na nowoczesnych procesorach. tak więc, zakładając, że instrukcja divide / Rest pochłonie większość czasu, różnica z powodu opóźnienia dodania będzie niewielka.

 3
Author: Goran D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-22 16:31:18

Obecnie operacje liczb całkowitych są zwykle nieco szybsze niż operacje zmiennoprzecinkowe. Jeśli więc możesz wykonać obliczenia z tymi samymi operacjami w liczbie całkowitej i zmiennoprzecinkowej, użyj liczby całkowitej. Jednak mówisz "to powoduje wiele irytujących problemów i dodaje wiele irytującego kodu". Wygląda na to, że potrzebujesz więcej operacji, ponieważ używasz arytmetyki całkowitej zamiast zmiennoprzecinkowej. W takim przypadku zmiennoprzecinkowy będzie działał szybciej, ponieważ

  • Jak najszybciej więcej operacji integer, prawdopodobnie potrzebujesz o wiele więcej, więc niewielka przewaga prędkości jest więcej niż zjedzona przez dodatkowe operacje

  • Kod zmiennoprzecinkowy jest prostszy, co oznacza, że pisanie kodu jest szybsze, co oznacza, że jeśli szybkość jest krytyczna, możesz poświęcić więcej czasu na optymalizację kodu.

 3
Author: gnasher729,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-28 18:16:49

Bazując na tym oh-so-niezawodnym "czymś, co słyszałem", w dawnych czasach obliczanie liczb całkowitych było około 20 do 50 razy szybsze niż zmiennoprzecinkowe, a obecnie jest mniej niż dwa razy szybsze.

 -1
Author: James Curran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-31 03:24:18