2008-11-07 18 views
6

Thông qua profiling tôi đã phát hiện ra rằng sprintf ở đây mất một thời gian dài. Có một phương án hoạt động tốt hơn vẫn xử lý các số không đứng đầu trong các trường y/m/d h/m/s không?Làm cách nào để cải thiện/thay thế sprintf, mà tôi đã đo là điểm phát sóng hiệu suất?

SYSTEMTIME sysTime; 
GetLocalTime(&sysTime); 
char buf[80]; 
for (int i = 0; i < 100000; i++) 
{ 

    sprintf(buf, "%4d-%02d-%02d %02d:%02d:%02d", 
     sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
     sysTime.wHour, sysTime.wMinute, sysTime.wSecond); 

} 

Lưu ý: Các OP giải thích trong các ý kiến ​​rằng đây là một ví dụ lột xuống. Vòng lặp "thực" chứa mã bổ sung sử dụng các giá trị thời gian khác nhau từ cơ sở dữ liệu. Tiểu sử đã xác định chính xác sprintf() là người phạm tội.

+0

Bao lâu là một "thời gian dài "? Tôi hy vọng nó sẽ được tính bằng micro giây thay vì mili giây (tùy thuộc vào CPU) – Roddy

+0

Bạn nên tìm cách để gọi sprintf ít thường xuyên hơn. – Brian

Trả lời

18

Nếu bạn đang viết hàm của riêng mình để thực hiện công việc, bảng tra cứu các giá trị chuỗi 0 .. 61 sẽ tránh phải thực hiện bất kỳ số học nào cho tất cả mọi thứ ngoài năm.

chỉnh sửa: Lưu ý rằng để đối phó với bước nhảy vọt giây (và để phù hợp với strftime()), bạn sẽ có thể in giây giá trị của 60 và 61.

char LeadingZeroIntegerValues[62][] = { "00", "01", "02", ... "59", "60", "61" }; 

Ngoài ra, làm thế nào về strftime()? Tôi không có ý tưởng làm thế nào hiệu suất so sánh (nó cũng có thể chỉ được gọi sprintf()), nhưng nó có giá trị nhìn vào (và nó có thể làm việc tra cứu ở trên chính nó).

+3

+1 từ kỹ sư nhúng cũ. Khi thời gian quan trọng hơn kích thước, thật khó để đánh bại một bảng tra cứu! –

+0

Rất đẹp. Nhưng hãy cẩn thận cách bạn sử dụng nó. Gọi strcat() để nối thêm các chuỗi cũng có khả năng xấu xí, hiệu suất khôn ngoan. – Roddy

+0

Vâng, vì bạn biết chúng đều là 2 nhân vật, bạn chỉ có thể ghi nhớ. Ngoài ra, một cái gì đó vừa xảy ra với tôi trong khi nhìn vào trang strtime, mảng chuỗi có lẽ nên lên đến "61", vì strtime làm điều đó, có lẽ là tài khoản cho giây nhuận. –

6

Bạn có thể thử điền từng lần lượt vào đầu ra.

buf[0] = (sysTime.wYear/1000) % 10 + '0' ; 
buf[1] = (sysTime.wYear/100) % 10 + '0'; 
buf[2] = (sysTime.wYear/10) % 10 + '0'; 
buf[3] = sysTime.wYear % 10 + '0'; 
buf[4] = '-'; 

... vv ...

Không đẹp, nhưng bạn sẽ có được hình ảnh. Nếu không có gì khác, nó có thể giúp giải thích tại sao sprintf sẽ không nhanh như vậy.

OTOH, có thể bạn có thể lưu vào bộ nhớ cache kết quả cuối cùng. Bằng cách đó, bạn chỉ cần tạo một giây một lần.

+0

Trong thực tế, giá trị thời gian là khác nhau mỗi lần lặp của vòng lặp (từ một db). Tôi chỉ đơn giản hóa mã cho ví dụ, –

6

Printf cần xử lý nhiều định dạng khác nhau. Bạn chắc chắn có thể lấy source for printf và sử dụng nó làm cơ sở để cuộn phiên bản của riêng bạn giao dịch cụ thể với cấu trúc sysTime. Bằng cách đó bạn vượt qua trong một đối số, và nó chỉ chính xác công việc cần được thực hiện và không có gì hơn.

1

Có thể bạn sẽ nhận được sự gia tăng mức độ hoàn hảo bằng cách di chuyển một con số trong buf trả về, vì bạn có thể tránh phân tích cú pháp chuỗi định dạng nhiều lần và sẽ không phải đối phó với nhiều trường hợp phức tạp hơn sprintf tay cầm. Tôi không thích thực sự khuyên bạn nên làm điều đó.

tôi sẽ khuyên bạn nên cố gắng tìm hiểu xem bằng cách nào đó bạn có thể làm giảm số tiền bạn cần phải tạo ra những chuỗi, là họ bắt buộc somegtimes, chúng có thể được lưu trữ vv

0

Thật khó để tưởng tượng rằng bạn đang đi để đánh bại sprintf ở định dạng số nguyên. Bạn có chắc chắn sprintf là vấn đề của bạn?

+0

Nó không định dạng số nguyên sprintf mất thời gian của nó trên, nó phân tích chuỗi định dạng. Vì nó không thay đổi trong vòng lặp, phân tích nó mỗi lần xung quanh là không cần thiết. –

2

Ý anh là gì trong một thời gian "dài" - kể từ khi sprintf() là báo cáo kết quả chỉ trong vòng lặp của bạn và "ống nước" của vòng lặp (increment, so sánh) là không đáng kể, các sprintf() để tiêu thụ phần lớn thời gian.

Hãy nhớ câu chuyện đùa cũ về người đàn ông đã mất chiếc nhẫn cưới của mình trên phố số 3 một đêm, nhưng tìm kiếm nó vào ngày 5 vì ánh sáng sáng hơn ở đó? Bạn đã xây dựng một ví dụ được thiết kế để "chứng minh" giả định của bạn rằng sprintf() không hiệu quả.

Kết quả của bạn sẽ chính xác hơn nếu bạn lập cấu hình mã "thực" có chứa sprintf() ngoài tất cả các chức năng và thuật toán khác mà bạn sử dụng. Ngoài ra, hãy thử viết phiên bản của riêng bạn để giải quyết chuyển đổi số không đệm cụ thể mà bạn yêu cầu.

Bạn có thể ngạc nhiên trước kết quả.

+0

Đoạn mã là một ví dụ đơn giản. Trong ví dụ thực tế, có nhiều thứ trong vòng lặp. (char *) _bstr_t, itoa .... Đó là cuộc chạy nước rút ở đây thật tệ. –

+0

Có phải Einstein đã nói mọi thứ nên được làm đơn giản nhất có thể, nhưng không đơn giản hơn? :-) Đã chỉnh sửa câu hỏi của bạn để phản ánh điều này. –

1

Làm cách nào để lưu vào bộ nhớ cache kết quả? Đó không phải là một khả năng sao? Xem xét rằng cuộc gọi sprintf() đặc biệt này được thực hiện quá thường xuyên trong mã của bạn, tôi giả định rằng giữa hầu hết các cuộc gọi liên tiếp này, năm, tháng và ngày không thay đổi.

Do đó, chúng tôi có thể triển khai một số nội dung như sau. Khai báo một tuổi và một cấu trúc SYSTEMTIME hiện tại:

SYSTEMTIME sysTime, oldSysTime; 

Ngoài ra, tuyên bố phần riêng biệt để giữ ngày và thời gian:

char datePart[80]; 
char timePart[80]; 

Đối với, lần đầu tiên, bạn sẽ phải điền vào cả hai sysTime, oldSysTime cũng như datePart và timePart. Nhưng sau đó sprintf() có thể được thực hiện khá nhanh như được đưa ra dưới đây:

sprintf (timePart, "%02d:%02d:%02d", sysTime.wHour, sysTime.wMinute, sysTime.wSecond); 
if (oldSysTime.wYear == sysTime.wYear && 
    oldSysTime.wMonth == sysTime.wMonth && 
    oldSysTime.wDay == sysTime.wDay) 
    { 
    // we can reuse the date part 
    strcpy (buff, datePart); 
    strcat (buff, timePart); 
    } 
else { 
    // we need to regenerate the date part as well 
    sprintf (datePart, "%4d-%02d-%02d", sysTime.wYear, sysTime.wMonth, sysTime.wDay); 
    strcpy (buff, datePart); 
    strcat (buff, timePart); 
} 

memcpy (&oldSysTime, &sysTime, sizeof (SYSTEMTIME)); 

Đoạn mã trên có một số dư thừa để làm cho mã dễ hiểu hơn. Bạn có thể yếu tố ra dễ dàng. Bạn có thể tăng tốc thêm nếu bạn biết rằng ngay cả giờ và phút sẽ không thay đổi nhanh hơn cuộc gọi của bạn đến thường lệ.

+0

không. Đoạn mã của tôi được đơn giản hóa từ thực tế. –

1

tôi sẽ làm một vài điều ...

  • bộ nhớ cache thời điểm hiện tại, do đó bạn không cần phải tạo lại dấu thời gian mỗi lần
  • làm việc chuyển đổi thời gian bằng tay. Phần chậm nhất của các hàm printf -family là phân tích chuỗi định dạng và thật ngớ ngẩn khi sử dụng các chu trình phân tích cú pháp đó cho mỗi lần thực hiện vòng lặp.
  • thử sử dụng bảng tra cứu 2 byte cho tất cả các chuyển đổi ({ "00", "01", "02", ..., "99" }). Điều này là do bạn muốn tránh số học moduluar, và một bảng 2 byte có nghĩa là bạn chỉ phải sử dụng một modulo, cho năm.
2

Có vẻ như Jaywalker đang đề xuất một phương pháp rất giống nhau (đánh bại tôi ít hơn một giờ).

Ngoài phương pháp bảng tra cứu đã được đề xuất (mảng n2s [] bên dưới), cách tạo bộ đệm định dạng sao cho sprintf thông thường ít chuyên sâu hơn? Mã dưới đây sẽ chỉ phải điền vào phút và giây mỗi lần qua vòng lặp trừ khi năm/tháng/ngày/giờ đã thay đổi. Rõ ràng, nếu bất kỳ của những người đã thay đổi bạn có một hit sprintf nhưng tổng thể nó có thể không nhiều hơn những gì bạn đang chứng kiến ​​(khi kết hợp với tra cứu mảng).


static char fbuf[80]; 
static SYSTEMTIME lastSysTime = {0, ..., 0}; // initialize to all zeros. 

for (int i = 0; i < 100000; i++) 
{ 
    if ((lastSysTime.wHour != sysTime.wHour) 
    || (lastSysTime.wDay != sysTime.wDay) 
    || (lastSysTime.wMonth != sysTime.wMonth) 
    || (lastSysTime.wYear != sysTime.wYear)) 
    { 
     sprintf(fbuf, "%4d-%02s-%02s %02s:%%02s:%%02s", 
       sysTime.wYear, n2s[sysTime.wMonth], 
       n2s[sysTime.wDay], n2s[sysTime.wHour]); 

     lastSysTime.wHour = sysTime.wHour; 
     lastSysTime.wDay = sysTime.wDay; 
     lastSysTime.wMonth = sysTime.wMonth; 
     lastSysTime.wYear = sysTime.wYear; 
    } 

    sprintf(buf, fbuf, n2s[sysTime.wMinute], n2s[sysTime.wSecond]); 

} 
+0

Mới đối với điều này ... toàn bộ câu trả lời của tôi có hiển thị cho bất kỳ ai không? Đối với tôi, có vẻ như chỉ có khoảng 4 dòng được hiển thị. – shank

+0

bạn cần sử dụng < for < and > cho> – quinmars

+0

Aah, tất nhiên. Cảm ơn. – shank

1

Tôi đang làm việc trên một vấn đề tương tự tại thời điểm này.

Tôi cần phải ghi nhật ký gỡ lỗi với dấu thời gian, tên tệp, số dòng, v.v. trên hệ thống được nhúng. Chúng tôi đã có một logger tại chỗ nhưng khi tôi xoay núm để 'đầy đủ đăng nhập', nó ăn tất cả các chu kỳ proc của chúng tôi và đặt hệ thống của chúng tôi trong tình trạng nghiêm trọng, nói rằng không có thiết bị máy tính cần phải trải nghiệm.

Ai đó đã nói "Bạn không thể đo lường/quan sát điều gì đó mà không thay đổi thứ bạn đang đo/quan sát".

Vì vậy, tôi đang thay đổi mọi thứ để cải thiện hiệu suất. Trạng thái hiện tại của mọi thứ là Im 2x nhanh hơn lệnh gọi hàm gốc (nút cổ chai trong hệ thống ghi nhật ký đó không có trong hàm gọi nhưng trong trình đọc nhật ký là tệp thực thi riêng biệt, mà tôi có thể hủy nếu tôi viết đăng nhập stack).

Giao diện tôi cần cung cấp là - void log(int channel, char *filename, int lineno, format, ...). Tôi cần phải gắn thêm tên kênh (hiện đang thực hiện tìm kiếm tuyến tính trong một danh sách! Đối với mỗi câu lệnh gỡ lỗi!) Và dấu thời gian bao gồm bộ đếm mili giây. Dưới đây là một số điều mà Im đang thực hiện để làm cho việc này nhanh hơn-

  • Làm xâu tên kênh để tôi có thể strcpy thay vì tìm kiếm trong danh sách. xác định macro LOG(channel, ...etc) làm log(#channel, ...etc). Bạn có thể sử dụng memcpy nếu bạn sửa chiều dài của chuỗi bằng cách xác định LOG(channel, ...)log("...."#channel - sizeof("...."#channel) + *11*) để cố định độ dài kênh byte
  • Tạo chuỗi thời gian một vài giây một giây. Bạn có thể sử dụng asctime hoặc một cái gì đó. Sau đó, memcpy chuỗi dài cố định cho mỗi câu lệnh debug.
  • Nếu bạn muốn tạo chuỗi dấu thời gian trong thời gian thực thì bảng tra cứu có gán (không phải memcpy!) Là hoàn hảo. Nhưng nó chỉ hoạt động với 2 chữ số và có thể cho năm.
  • Còn ba chữ số (mili giây) và năm chữ số (lineno) thì sao? Tôi không thích itoa và tôi không thích itoa tùy chỉnh (digit = ((value /= value) % 10)) hoặc vì div và mod là chậm. Tôi đã viết các chức năng dưới đây và sau đó phát hiện ra rằng một cái gì đó tương tự là trong hướng dẫn tối ưu hóa AMD (trong lắp ráp) mà cho tôi sự tự tin rằng đây là những triển khai C nhanh nhất.

    void itoa03(char *string, unsigned int value) 
    { 
        *string++ = '0' + ((value = value * 2684355) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = ' ';/* null terminate here if thats what you need */ 
    } 
    

    Tương tự như vậy, đối với những con số dòng,

    void itoa05(char *string, unsigned int value) 
    { 
        *string++ = ' '; 
        *string++ = '0' + ((value = value * 26844 + 12) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = ' ';/* null terminate here if thats what you need */ 
    } 
    

Nhìn chung, mã của tôi là khá nhanh bây giờ. vsnprintf() Tôi cần sử dụng mất khoảng 91% thời gian và phần còn lại của mã của tôi chỉ chiếm 9% (trong khi phần còn lại của mã ngoại trừ vsprintf() được sử dụng để lấy 54% trước đó)

+0

Điều này thật tuyệt vời - cảm ơn rất nhiều –

+0

Bạn có thể giải thích những con số ma thuật đó không? – someonewithpc

Các vấn đề liên quan