簡體   English   中英

內置類型的性能:char vs short vs int vs. float vs. double

[英]Performance of built-in types : char vs short vs int vs. float vs. double

這可能看起來有點愚蠢但是看到Alexandre C在其他主題中的回復 ,我很想知道如果內置類型有任何性能差異:

char vs short vs int vs. float vs. double

通常我們在現實生活中沒有考慮這種性能差異(如果有的話),但我想知道這是出於教育目的。 可以問的一般問題是:

  • 整數算術和浮點運算之間是否有任何性能差異?

  • 哪個更快? 更快的原因是什么? 請解釋一下。

浮點數與整數:

從歷史上看,浮點可能比整數運算慢得多。 在現代計算機上,情況已經不再如此(在某些平台上速度稍慢,但除非您編寫完美的代碼並針對每個周期進行優化,否則差異將被代碼中的其他低效率所淹沒)。

在有限的處理器上,如高端手機中的處理器,浮點可能比整數慢一些,但只要有硬件浮點可用,它通常在一個數量級(或更好)的范圍內。 值得注意的是,隨着手機被要求運行越來越多的通用計算工作負載,這種差距正在迅速縮小。

非常有限的處理器(便宜的手機和烤面包機)上,通常沒有浮點硬件,因此需要在軟件中模擬浮點運算。 這很慢 - 比整數算術慢幾個數量級。

正如我所說,人們期望他們的手機和其他設備的行為越來越像“真正的計算機”,硬件設計師正在迅速加強FPU以滿足這種需求。 除非您追逐每個循環,或者您正在為極少或沒有浮點支持的非常有限的CPU編寫代碼,否則性能差異對您來說無關緊要。

不同大小的整數類型:

通常, CPU在其原始字大小的整數上運行速度最快(有一些關於64位系統的警告)。 在現代CPU上,32位操作通常比8位或16位操作更快,但這在架構之間會有很大差異。 另外,請記住,您無法單獨考慮CPU的速度; 它是復雜系統的一部分。 即使在16位數上運行比在32位數上運行慢2倍,當您使用16位數而不是32位表示數據時,可以將兩倍的數據放入緩存層次結構中。 如果這使得所有數據都來自緩存而不是經常緩存未命中之間的區別,那么更快的內存訪問將勝過CPU的較慢操作。

其他說明:

矢量化進一步提升了平衡,支持更窄的類型( float和8位和16位整數) - 您可以在相同寬度的矢量中執行更多操作。 但是,良好的矢量代碼很難編寫,因此,如果沒有大量細致的工作,就不會獲得這種好處。

為什么會出現性能差異?

實際上只有兩個因素會影響CPU上的操作是否快速:操作的電路復雜性以及用戶對操作的快速需求。

(在合理范圍內)如果芯片設計者願意在這個問題上投入足夠的晶體管,那么任何操作都可以快速完成。 但晶體管需要花錢(或者更確切地說,使用大量晶體管會使您的芯片變大,這意味着您每個晶圓的芯片數量減少,產量降低,這會花費金錢),因此芯片設計人員必須平衡使用多少操作的復雜性,以及他們根據(感知的)用戶需求來做這件事。 粗略地說,您可能會考慮將操作分為四類:

                 high demand            low demand
high complexity  FP add, multiply       division
low complexity   integer add            popcount, hcf
                 boolean ops, shifts

高需求,低復雜度的操作幾乎可以在任何CPU上快速運行:它們是低成本的結果,並為每個晶體管帶來最大的用戶利益。

高需求,高復雜度的操作將在昂貴的CPU(如計算機中使用的CPU)上快速運行,因為用戶願意為它們付費。 你可能不願意為你的烤面包機額外支付3美元來快速增加FP,但是,如此便宜的CPU會吝嗇這些指令。

幾乎所有處理器的低需求,高復雜度操作通常都會很慢; 沒有足夠的好處來證明成本合理。

如果有人不願意考慮它們,那么低需求,低復雜度的操作會很快,否則就不存在。

進一步閱讀:

  • Agner Fog維護着一個很好的網站,其中包含大量關於低級性能細節的討論(並且有非常科學的數據收集方法來支持它)。
  • 英特爾®64和IA-32架構優化參考手冊 (PDF下載鏈接是頁面的一部分)也涵蓋了很多這些問題,盡管它專注於一個特定的體系結構系列。

絕對。

首先,當然,它完全取決於所討論的CPU架構。

但是,積分和浮點類型的處理方式非常不同,因此以下情況幾乎總是如此:

  • 對於簡單的操作,積分類型很快 例如,整數加法通常只有一個周期的延遲,整數乘法通常約為2-4個周期,即IIRC。
  • 浮點類型用於執行速度慢得多。 然而,在今天的CPU上,它們具有出色的吞吐量,並且每個浮點單元通常可以在每個周期中退出操作,從而導致與整數運算相同(或類似)的吞吐量。 但是,延遲通常更糟。 浮點加法通常具有大約4個周期的延遲(對於整數而言為1)。
  • 對於一些復雜的操作,情況是不同的,甚至是逆轉的。 例如,對FP的划分可能具有比整數更少的延遲,這僅僅是因為在兩種情況下操作都很復雜,但它在FP值上更常用,因此可以花費更多精力(和晶體管)來優化該情況。

在某些CPU上,雙精度可能比浮點數慢得多。 在某些體系結構中,沒有用於雙精度的專用硬件,因此它們通過傳遞兩個浮點大小的塊來處理,從而使您的吞吐量更低,延遲時間延長兩倍。 在其他(例如x86 FPU)上,兩種類型都轉換為相同的內部格式80位浮點(在x86的情況下),因此性能相同。 在其他情況下,float和double都有適當的硬件支持,但由於float具有較少的位,因此可以更快地完成,通常相對於雙操作減少一點延遲。

免責聲明:所有提及的時間和特征都是從內存中提取的。 我看起來沒什么,所以可能是錯的。 ;)

對於不同的整數類型,答案根據CPU架構而有很大差異。 x86架構由於其悠久的歷史,必須本身支持8,16,32(以及今天的64)位操作,並且通常它們都同樣快速(它們使用基本相同的硬件,並且只有零根據需要輸出高位。

但是,在其他CPU上,小於int數據類型加載/存儲的成本可能更高(向內存寫入一個字節可能必須通過加載它所在的整個32位字來完成,然后進行位掩碼更新寄存器中的單個字節,然后將整個字寫回)。 同樣,對於大於int數據類型,某些CPU可能必須將操作拆分為兩個,分別加載/存儲/計算下半部分和上半部分。

但是在x86上,答案是它並不重要。 由於歷史原因,CPU需要對每種數據類型都提供非常強大的支持。 因此,您可能注意到的唯一區別是浮點運算具有更多延遲(但吞吐量相似,因此它們本身並不 ,至少如果您正確編寫代碼)

我認為沒有人提到整數推廣規則。 在標准C / C ++中,不能對小於int的類型執行任何操作。 如果char或short恰好小於當前平台上的int,則它們被隱式提升為int(這是bug的主要來源)。 編譯器需要進行隱式升級,沒有違反標准就無法繞過它。

整數提升意味着語言中的操作(加法,按位,邏輯等)不會出現在比int更小的整數類型上。 因此,對char / short / int的操作通常同樣快,因為前者被提升為后者。

除了整數提升之外,還有“通常的算術轉換”,這意味着C努力使兩個操作數相同,如果它們不同,則將其中一個轉換為兩者中較大的一個。

但是,CPU可以在8,16,32等級執行各種加載/存儲操作。 在8位和16位架構上,這通常意味着盡管有整數提升,但8位和16位類型更快。 在32位CPU上,它實際上可能意味着較小的類型較慢 ,因為它希望將所有內容整齊地排列在32位塊中。 32位編譯器通常優化速度,並在比指定空間更大的空間中分配更小的整數類型。

雖然通常較小的整數類型當然比較大的整數類型占用更少的空間,所以如果你打算優化RAM大小,他們更喜歡。

上面的第一個答案很棒,我把它的一小部分復制到下面的副本中(因為這是我最先結束的地方)。

“char”和“small int”比“int”慢嗎?

我想提供以下代碼,分析,初始化和對各種整數大小進行一些算術運算:

#include <iostream>

#include <windows.h>

using std::cout; using std::cin; using std::endl;

LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
LARGE_INTEGER Frequency;

void inline showElapsed(const char activity [])
{
    QueryPerformanceCounter(&EndingTime);
    ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
    ElapsedMicroseconds.QuadPart *= 1000000;
    ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;
    cout << activity << " took: " << ElapsedMicroseconds.QuadPart << "us" << endl;
}

int main()
{
    cout << "Hallo!" << endl << endl;

    QueryPerformanceFrequency(&Frequency);

    const int32_t count = 1100100;
    char activity[200];

    //-----------------------------------------------------------------------------------------//
    sprintf_s(activity, "Initialise & Set %d 8 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    int8_t *data8 = new int8_t[count];
    for (int i = 0; i < count; i++)
    {
        data8[i] = i;
    }
    showElapsed(activity);

    sprintf_s(activity, "Add 5 to %d 8 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    for (int i = 0; i < count; i++)
    {
        data8[i] = i + 5;
    }
    showElapsed(activity);
    cout << endl;
    //-----------------------------------------------------------------------------------------//

    //-----------------------------------------------------------------------------------------//
    sprintf_s(activity, "Initialise & Set %d 16 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    int16_t *data16 = new int16_t[count];
    for (int i = 0; i < count; i++)
    {
        data16[i] = i;
    }
    showElapsed(activity);

    sprintf_s(activity, "Add 5 to %d 16 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    for (int i = 0; i < count; i++)
    {
        data16[i] = i + 5;
    }
    showElapsed(activity);
    cout << endl;
    //-----------------------------------------------------------------------------------------//

    //-----------------------------------------------------------------------------------------//    
    sprintf_s(activity, "Initialise & Set %d 32 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    int32_t *data32 = new int32_t[count];
    for (int i = 0; i < count; i++)
    {
        data32[i] = i;
    }
    showElapsed(activity);

    sprintf_s(activity, "Add 5 to %d 32 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    for (int i = 0; i < count; i++)
    {
        data32[i] = i + 5;
    }
    showElapsed(activity);
    cout << endl;
    //-----------------------------------------------------------------------------------------//

    //-----------------------------------------------------------------------------------------//
    sprintf_s(activity, "Initialise & Set %d 64 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    int64_t *data64 = new int64_t[count];
    for (int i = 0; i < count; i++)
    {
        data64[i] = i;
    }
    showElapsed(activity);

    sprintf_s(activity, "Add 5 to %d 64 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    for (int i = 0; i < count; i++)
    {
        data64[i] = i + 5;
    }
    showElapsed(activity);
    cout << endl;
    //-----------------------------------------------------------------------------------------//

    getchar();
}


/*
My results on i7 4790k:

Initialise & Set 1100100 8 bit integers took: 444us
Add 5 to 1100100 8 bit integers took: 358us

Initialise & Set 1100100 16 bit integers took: 666us
Add 5 to 1100100 16 bit integers took: 359us

Initialise & Set 1100100 32 bit integers took: 870us
Add 5 to 1100100 32 bit integers took: 276us

Initialise & Set 1100100 64 bit integers took: 2201us
Add 5 to 1100100 64 bit integers took: 659us
*/

我在i7 4790k上的MSVC結果:

Initialise&Set 1100100 8位整數:444us
添加5到1100100 8位整數:358us

初始化和設置1100100 16位整數:666us
添加5到1100100 16位整數:359us

初始化和設置1100100 32位整數:870us
添加5到1100100 32位整數:276us

初始化和設置1100100 64位整數:2201us
添加5到1100100 64位整數:659us

整數算術和浮點運算之間是否有任何性能差異?

是。 但是,這是非常平台和CPU特定的。 不同平台可以以不同的速度執行不同的算術運算。

話雖如此,有問題的答復有點具體。 pow()是一個通用的例程,適用於double值。 通過提供整數值,它仍然可以完成處理非整數指數所需的所有工作。 使用直接乘法會繞過很多復雜性,這就是速度發揮作用的地方。 這實際上不是一個(不是很多)不同類型的問題,而是繞過了使用任何指數制作pow函數所需的大量復雜代碼。

取決於處理器和平台的組成。

具有浮點協處理器的平台可能比積分算術慢,因為必須將值傳送到協處理器和從協處理器傳送值。

如果浮點處理在處理器的核心內,則執行時間可以忽略不計。

如果浮點計算由軟件模擬,那么積分算法將更快。

如有疑問,請查看。

在優化之前使編程正常且穩健。

不,不是真的。 這當然取決於CPU和編譯器,但性能差異通常可以忽略不計 - 如果有的話。

浮點和整數運算之間肯定存在差異。 根據CPU的特定硬件和微指令,您可以獲得不同的性能和/或精度。 精確描述的好谷歌術語(我也不確切):

FPU x87 MMX SSE

關於整數的大小,最好使用平台/體系結構字大小(或兩倍),它在x86上為int32_t ,在x86_64上為int64_t SOme處理器可能具有一次性處理這些值的內部指令(如SSE(浮點)和MMX),這將加速並行加法或乘法。

通常,整數數學比浮點數學更快。 這是因為整數數學涉及更簡單的計算。 但是,在大多數操作中,我們談論的是不到十幾個時鍾。 不是毫克,微米,納米或蜱; 時鍾。 在現代核心中每秒發生2-3億次的情況。 此外,由於486許多內核具有一組浮點處理單元或FPU,它們通過硬連線有效地執行浮點運算,並且通常與CPU並行。

由於這些原因,雖然技術上它的速度較慢,但​​浮點計算仍然如此之快,以至於任何計時差異的嘗試都會在計時機制和線程調度中產生比實際執行計算所需的更多錯誤。 盡可能使用int,但在不能的時候理解,並且不要過分擔心相對計算速度。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM