Sanırım soru sahibinin daha farklı bir cevabı bulmasının daha yararlı olacağını düşünüyorum, çünkü sorularda ve bazı cevaplarda veya yorumlarda incelenmemiş bazı varsayımlar görüyorum.
Sonuçta ortaya çıkan göreceli kaydırma ve çarpma çalışma süresinin C ile hiçbir ilgisi yoktur. C derken, bunun ya da GCC'nin o sürümü gibi belirli bir uygulamanın örneği değil, dilin anlamına gelmez. Bu reklamı saçmalamak istemem, ama örnekleme için aşırı bir örnek kullanmak isterim: tamamen standartlara uygun bir C derleyicisi uygulayabilir ve çarpma bir milisaniyeyi veya diğer yollardan giderken bir saat sürebilir. C veya C ++ ile bu tür performans kısıtlamaları farkında değilim.
Tartışmada bu teknikliği önemsemezsiniz. Amacınız muhtemelen çarpmalara karşı vardiya yapmanın göreceli performansını test etmekti ve C'yi seçtiniz, çünkü genellikle düşük seviyeli bir programlama dili olarak algılanıyor, bu nedenle kaynak kodunun doğrudan ilgili talimatlara daha doğrudan çevrilmesini bekleyebilirsiniz. Bu tür sorular çok yaygındır ve bence iyi bir cevap, C kodunda bile kaynak kodunuzun verilen bir durumda düşündüğünüz gibi doğrudan talimatlara çevrilmediğine işaret etmelidir. Aşağıda size bazı olası derleme sonuçları verdim.
Gerçek dünyadaki yazılımda bu eşdeğeri yerine kullanmanın yararını sorgulayan yorumlar burada gelir. Eric Lippert'den olduğu gibi, sorunuzun yorumlarında bazılarını görebilirsiniz. Bu tür optimizasyonlara cevap olarak genellikle daha tecrübeli mühendislerden alacağınız reaksiyonla uyumludur. Üretim kodunda ikili vardiyayı çarpma ve bölme aracı olarak kullanırsanız, insanlar kodunuzda büyük olasılıkla kodlar ve bir dereceye kadar duygusal tepkiler yaşarlar ("Cennetin uğruna JavaScript ile ilgili bu saçma iddiayı duydum.") Bu tepkilerin nedenlerini daha iyi anlamadıkça, programcıları yeni başlayanlar için mantıklı gelmeyebilir.
Bu nedenler, esasen, göreceli performanslarını karşılaştırırken bulmuş olabileceğiniz için, bu tür optimizasyonların okunabilirliği ve düşüklüğünün bir birleşimidir. Ancak, eğer çarpma için vardiya değişimi bu tür optimizasyonların tek örneği ise, insanların bir tepkiden daha güçlü olacağını düşünmüyorum. Sizinki gibi sorular sıklıkla çeşitli biçimlerde ve çeşitli bağlamlarda ortaya çıkar. Sanırım daha kıdemli mühendislerin gerçekte bu kadar güçlü tepki göstermesinin, en azından zaman zaman sahip olduğum gibi, insanlar kod bazında bu tür mikro optimizasyonları liberal bir şekilde kullandıklarında daha geniş bir zarar aralığı potansiyeli olduğunu düşünüyorum. Microsoft gibi bir şirkette büyük bir kod tabanında çalışıyorsanız, diğer mühendislerin kaynak kodlarını okumak veya burada belirli bir kodu bulmaya çalışmak için çok zaman harcarsınız. Hatta birkaç yıl sonra, özellikle çağrı yapmadan yapılan bir aramayı takiben bir üretim kesintisini düzeltmek zorunda kaldığınızda olduğu gibi, en uygun olmayan zamanlarda, bir şeyler ifade etmeye çalışacağınız kendi kodunuz bile olabilir. Cuma gecesi görev, arkadaşlarla eğlenmek için bir geceye çıkmak üzere… Kod okumaya çok zaman harcıyorsanız, mümkün olduğu kadar okunaklı olduğunu takdir edeceksiniz. En sevdiğiniz romanı okuduğunuzu hayal edin, ancak yayıncı, abbrv kullandıkları yeni bir sürüm yayınlamaya karar verdi. tüm ov th pl bc thc thnk ss spc. Bu, diğer mühendislerin kodunuza verebilecekleri tepkilere benzer; bu tür optimizasyonlarla onları serpiştirirseniz. Diğer cevapların da belirttiği gibi, ne demek istediğinizi açıkça belirtmek daha iyidir.
Bu ortamlarda bile, kendinizi veya başka bir denkliği bilmeniz beklenen bir röportaj sorusu çözerken kendinizi bulabilirsiniz. Bunları bilmek kötü değildir ve iyi bir mühendis, ikili kaymanın aritmetik etkisinin farkında olacaktır. Bunun bence iyi bir mühendis olduğunu söylemediğimi, ancak bence iyi bir mühendisin bileceğini unutmayın. Özellikle de, bu akıllı mühendislik "hilesini" kodlama sorusunda size göstermeyi ve kendisinin kanıtladığını kanıtlamaktan memnuniyet duyacağınızı umarak, genellikle görüşme döngüsünüzün sonuna doğru, bazı yöneticiler bulabilirsiniz. Ayrıca, eskiden beri "yönetici" değil, anlayışlı mühendislerden biriydi. Bu gibi durumlarda, etkilenmiş görünmeye çalışın ve aydınlatıcı röportaj için ona teşekkür edin.
C de neden bir hız farkı görmediniz? En muhtemel cevap, her ikisinin de aynı montaj koduyla sonuçlanmış olmasıdır:
int shift(int i) { return i << 2; }
int multiply(int i) { return i * 2; }
Her ikisi de içine derleyebilir
shift(int):
lea eax, [0+rdi*4]
ret
Optimizasyonları olmayan GCC'de, örneğin "-O0" bayrağını kullanarak, şunu alabilirsiniz:
shift(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
sal eax, 2
pop rbp
ret
multiply(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
add eax, eax
pop rbp
ret
Gördüğünüz gibi, "-O0" u GCC’ye geçirmek, ne tür bir kod ürettiği hakkında biraz akıllıca olacağı anlamına gelmez. Özellikle, bu durumda bile derleyicinin bir çarpma talimatı kullanmaktan kaçındığına dikkat edin. Aynı denemeyi başka sayılarla ve hatta iki sayının gücü olmayan sayılarla çarpmalarla tekrarlayabilirsiniz. Muhtemelen platformunuzda bir kayma ve ekleme kombinasyonu göreceksiniz, ancak çarpma yok. Eğer çarpma ve kaymalar gerçekten aynı maliyete sahipse, derleyicinin görünüşte çarpımları kullanmaktan kaçınması biraz tesadüf gibi görünüyor, değil mi? Ancak ispat için varsayım sağlamak istemem, o yüzden devam edelim.
Testinizi yukarıdaki kodla tekrar başlatabilir ve şimdi bir hız farkı görüp görmediğinizi görebilirsiniz. O zaman bile, çarpma ile çarpma testini test etmiyorsunuz, çarpma yokluğunu görerek görüyorsunuz, ancak GCC tarafından kayma C işlemleri için belirli bayrak dizileriyle oluşturulan ve belirli bir örnekte çarpma kodu . Böylece başka bir testte montaj kodunu elle düzenleyebilir ve bunun yerine "çarpma" yönteminin kodunda "imul" komutunu kullanabilirsiniz.
Derleyicinin bu akıllılarından bazılarını yenmek istersen, daha genel bir kayma ve çarpma yöntemi tanımlayabilir ve bunun gibi bir şeyle sonuçlanabilir:
int shift(int i, int j) { return i << j; }
int multiply(int i, int j) { return i * j; }
Aşağıdaki montaj kodunu verebilecek olan:
shift(int, int):
mov eax, edi
mov ecx, esi
sal eax, cl
ret
multiply(int, int):
mov eax, edi
imul eax, esi
ret
En sonunda, GCC 4.9'un en iyi optimizasyon seviyesinde bile, başlangıçta testinize başlarken beklediğiniz montaj talimatlarındaki ifadeye sahibiz. Performans optimizasyonunda kendi başına önemli bir ders olabileceğini düşünüyorum. Derleyicinin uygulayabileceği akıllılar açısından, kodumuzda somut sabitlerin yerine değişkenler koymak için yarattığı farkı görebiliriz. Kaydırmalı çarpma sübstitüsyonu gibi mikro optimizasyonlar, bir derleyicinin genellikle kendi başına kolayca yapabileceği çok düşük seviyeli optimizasyonlardır. Performans üzerinde çok daha etkili olan diğer optimizasyonlar , kodun amacının anlaşılmasını gerektirirBu derleyici tarafından genellikle erişilebilir değildir veya sadece bazı sezgiseller tarafından tahmin edilebilir. Bir yazılım mühendisi olarak buraya girersiniz ve kesinlikle tipik olarak çarpmaların çarpımlarını değiştirmeyi içermez. G / Ç üreten ve bir işlemi engelleyebilen bir hizmete fazladan bir çağrı yapılmaması gibi faktörleri içerir. Sabit diskinize ya da tanrınız, zaten bellekte olanlardan elde edebileceğiniz bazı ekstra veriler için uzaktaki bir veritabanına girmezseniz, beklediğiniz süre milyonlarca talimatın yerine getirilmesinden ağır basar. Şimdi, sanırım asıl sorunuzdan biraz sapmışız, ancak bunu bir soru sormaya işaret etmeyi düşünüyorum, özellikle de kodun tercümesi ve yürütülmesi hakkında yeni bir şeyler öğrenmeye başlıyorsak,
Peki hangisi daha hızlı olacak? Performans farkını gerçekten test etmeyi seçmenin iyi bir yaklaşım olduğunu düşünüyorum. Genel olarak, bazı kod değişikliklerinin çalışma zamanı performansından şaşırmak kolaydır. Modern işlemcilerin kullandığı birçok teknik vardır ve yazılımlar arasındaki etkileşim de karmaşık olabilir. Bir durumda belirli bir değişiklik için faydalı performans sonuçları alsanız bile, bu tür bir değişikliğin her zaman performans yararları sağlayacağı sonucuna varmanın tehlikeli olduğunu düşünüyorum. Bir kez böyle bir test yapmanın tehlikeli olduğunu düşünüyorum, "Tamam, şimdi hangisinin daha hızlı olduğunu biliyorum!" ve ardından, ölçümlerinizi tekrarlamadan, aynı optimizasyonu üretim koduna ayırt etmeden uygulayın.
Peki, vardiya çarpma işleminden daha hızlıysa ne olur? Bunun neden doğru olacağına dair kesinlikle belirtiler var. GCC, yukarıda gördüğünüz gibi, diğer talimatların lehine doğrudan çarpmadan kaçınmanın iyi bir fikir olduğunu düşünüyor (optimizasyon olmadan bile). Intel 64 ve IA-32 Mimarileri Optimizasyon Referans Kılavuzu size CPU talimatları nispi maliyeti hakkında fikir verecektir. Daha fazla gecikme ve verime odaklanan başka bir kaynak ise http://www.agner.org/optimize/instruction_tables.pdf. Bunların mutlak bir çalışma zamanı için iyi bir tahminci değil, birbirlerine göre talimatların performansının bir göstergesi olduğunu unutmayın. Sıkı bir döngüde, testiniz simüle edilirken, "verim" metriği en alakalı olmalıdır. Belirli bir talimatın yürütülmesi için bir yürütme biriminin tipik olarak bağlanacağı döngü sayısıdır.
Peki, vardiya çarpma işleminden daha hızlı değilse ne olur? Yukarıda söylediğim gibi, modern mimariler oldukça karmaşık olabilir ve dal tahmini, önbellekleme, boru hattı ve paralel yürütme birimleri gibi şeyler zaman zaman mantıksal olarak eşdeğer iki kod parçasının göreceli performansını tahmin etmeyi zorlaştırabilir. Bunu gerçekten vurgulamak istiyorum, çünkü bu, bunun gibi soruların cevaplarının çoğundan memnun olmadığım yer ve buradaki insanların kampının tamamen doğru olmadığını (artık) değiştirmenin çarpma işleminden daha hızlı olduğunu söylüyor.
Hayır, farkında olduğum kadarıyla 1970'lerde bazı gizli mühendislik sosu icat etmedik ya da ne zaman bir çarpma biriminin ve biraz değiştiricinin maliyet farkını aniden iptal edemedik. Mantıksal kapılar açısından ve kesinlikle mantıksal işlemler açısından genel bir çarpma, birçok senaryoda, birçok mimaride varil kaydırmalı bir kaymadan daha karmaşıktır. Bunun bir masaüstü bilgisayardaki genel çalışma zamanına nasıl çevrileceği biraz opak olabilir. Belirli işlemcilere nasıl uygulandıklarını tam olarak bilmiyorum, ancak işte çarpımın açıklaması: Tam sayı çarpımı, modern CPU'ya eklenmesiyle aynı hızda mı?
Burada bir Namlu Değiştiren açıklamasıdır . Önceki paragrafta atıfta bulunduğum belgeler CPU işlem proxy'si tarafından işlemlerin göreceli maliyeti hakkında başka bir görünüm sunar. Intel'deki mühendisler sık sık benzer sorular alıyor gibi görünüyor: intel geliştirici bölgesi , tamsayılı çarpma ve çekirdek 2 ikili işlemciye ekleme için saat döngüleri sunuyor
Evet, çoğu gerçek hayat senaryosunda ve neredeyse kesinlikle JavaScript’te performansın uğruna bu eşdeğerliği kullanmaya çalışmak muhtemelen boşuna bir girişimdir. Bununla birlikte, çarpma talimatlarını kullanmaya zorladıysak ve daha sonra çalışma süresinde hiçbir fark görmemiş olsak bile, bu, daha önce kullandığımız maliyet ölçümünün niteliği nedeniyle, kesin olmaktır, maliyet farkı olmadığı için değildir. Uçtan uca çalışma zamanı bir metriktir ve tek umursadığımız tek şeyse her şey yolunda. Ancak bu çarpma ve kayma arasındaki tüm maliyet farklılıklarının ortadan kalktığı anlamına gelmez. Ve bence bu fikri, sorgulayıcıya, ima yoluyla veya başka şekilde, modern kodun çalışma zamanına ve maliyetine dahil olan faktörler hakkında bir fikir edinmeye başlayan, kesinlikle doğru bir fikir değil. Mühendislik her zaman değişimlerle ilgilidir. Modern işlemcilerin, gördükleri kullanıcılar olarak gördüklerimizi yürütme zamanını göstermek için yaptıkları tradeollüklere ilişkin soruşturma ve açıklama, daha farklı bir cevap verebilir. Ve daha az mühendisin mikro-optimize edilmiş kodun okunabilirliğini ortadan kaldırarak denetlediğini görmek istiyorsak, "bu artık doğru değil" den daha farklı bir cevabın garanti edildiğini düşünüyorum çünkü bu tür "optimizasyonların" doğasını daha genel bir şekilde anlamayı gerektirir çeşitli, eski enkarnasyonlarını, güncel olmayan bazı özel örneklere atıfta bulunmaktan çok saptamak.