SECTREE::m_neighbor_list cache locality

Larry Watterson

Üye
Üye
Mesaj
56
Çözümler
1
Beğeni
189
Puan
699
Ticaret Puanı
0
Farklı bir forumda paylaşılan optimizasyon düzeltmesini incelerken gözüme çarpması üzerine;

"ForEachAround" bu hot path içinde çalışan bir func, bir bakayım dedim üstün zekalı YMIR geliştiricileri neighbor list'i iterate ediyor functor içinde.

C++:
Genişlet Daralt Kopyala
FCollectEntity collector;
#ifdef __clang__
    LPSECTREE_LIST::const_iterator it = m_neighbor_list.begin();
#else
    LPSECTREE_LIST::iterator it = m_neighbor_list.begin();
#endif
for ( ; it != m_neighbor_list.end(); ++it)
{
    LPSECTREE sectree = *it;
    sectree->for_each_entity(collector);
}
collector.ForEach(func);

"m_neighbor_list" bu bir std::list yani doubly linked list implementasyonu, ne yapıyor bu? "SECTREE_MAP::Build" fonksiyonunda yani harita yüklenirken içine en fazla 9 tane komşu (kendisi + 8 komşu) pushlanıyor ve sadece read-only iterate olarak kullanılmış, build'den sonra hiç sattei değişmiyor.

Şimdi akış şöyle: ForEachAround, UpdateSectree üzerinden hareket/spawn/teleport/state-değişimi gibi olaylarda tetikleniyor, her tickte değil ama bir hot path sayılabilir. N entity x en fazla 9 komşu desek baya bir pointer chasing + indirection maliyeti var. Zaten böyle bir senaryoda neden std::list tercih edersin ki? iterator invalidation ihtimali yok, reallocation derdin yok çünkü sadece tabiri caizse initialize aşamasında bir şeyler pushluyorsun.

-> komşu sayısı load time'da sabitleniyor(üst sınır 9 fonksiyona bakarsınız gözümden kaçtıysa daha fazla olabilir ama yok gibi)
-> doubly linked listin herhangi bir faydası kullanılmıyor (ortaya ekleme vs o(1), no memory reallocation vb.)

e o zaman neden std::list tercih edilmiş? bunun cevabı gerçekten Allah bilir, hiçbir mantıklı sebebi yok.

trick burdaki container seçimini düzeltip contiguous bir container (std::vector ya da sabit kapasiteli boost::container::static_vector, std::inplace_vector vb.) tercih edin. Tabii sonuçları ölçmeden performans kazancı olduğunu kimse iddia edemez mantıken toplam çağrı sayısı arttıkça falan faydası görülmeli, ölçün.

Elde edilecek faydalar:

No pointer chasing
ILP
bla bla bla. klasik cache locality faydaları.
 
Son düzenleme:
Konuyu şöyle inceliyorum

1786522630936.webp
 
Sizce ymir geliştiricileri "üstün zekalı" denerek ti ye alınacak tarzda geliştiriciler mi hocam.
Yıllardır duyarız kodda görünen yanlışlarda, hatalarda, mantıksızlıklarda ymir geliştiricilerine bi dokundurulur.
Sebebi nedir acaba 2000lerde yapılan ve görece yıllar boyunca hizmet edip para kazanmış bir oyunun geliştiricileri sizce kötü geliştiriciler mi?
 
Sizce ymir geliştiricileri "üstün zekalı" denerek ti ye alınacak tarzda geliştiriciler mi hocam.
Yıllardır duyarız kodda görünen yanlışlarda, hatalarda, mantıksızlıklarda ymir geliştiricilerine bi dokundurulur.
Sebebi nedir acaba 2000lerde yapılan ve görece yıllar boyunca hizmet edip para kazanmış bir oyunun geliştiricileri sizce kötü geliştiriciler mi?
Bilindiği üzere Metin2 C++ ve python olmak üzere çok geniş bir kod yapısına sahip. Buna ek olarak ymir'in bir ekip olduğunu düşünürsek, bir dosyaya 10 farklı el değmesi muhtemeldir. Bir dosyaya zaman içinde hem çok iyi hem de çok kötü bir yazılımcı müdahale etmiş olması yüksek ihtimal. İnsani faktörler de ayrı bir etki tabi.. Bu sebeple bu tür ithamlar genelde o tarz kişilere yapılıyor, tüm ekibe değil.

Basit bir constructor vakası:
muhtemelen hatuna yetişmek için acele eden bir geliştiricinin hızını alamayıp şöyle bir şey yapması gibi:
C++:
Genişlet Daralt Kopyala
CPythonBackground::CPythonBackground()
{
    m_dwRenderShadowTime=0;
    m_eViewDistanceNum=0; // ✓
    m_eViewDistanceNum=0; // ?
    m_eViewDistanceNum=0; // ???? burada ne yaptığının farkına varmış ve durmuş. ama yine de düzeltilmemiş :D
    ...
    ...
}
 
Sizce ymir geliştiricileri "üstün zekalı" denerek ti ye alınacak tarzda geliştiriciler mi hocam.
Yıllardır duyarız kodda görünen yanlışlarda, hatalarda, mantıksızlıklarda ymir geliştiricilerine bi dokundurulur.
Sebebi nedir acaba 2000lerde yapılan ve görece yıllar boyunca hizmet edip para kazanmış bir oyunun geliştiricileri sizce kötü geliştiriciler mi?
Evet, kötü geliştiriciler.

2000'ler bence güzel bir era, yaşım o günleri görmeye yetmedi ama çokça legacy codebase incelemişimdir, çok daha temiz projeler var emin olun. Metin2 özelinde yazılım mühendisliği açısından berbat bir yapısı var, tasarım yok, kod kalitesi yok, uyulmuş bi kural seti yok, anlayacağınız yok da yok.

Yazılım sektöründe ortalama düzeyin çok düşük olduğu bir gerçek etnik fark etmeksizin, çünkü kimse okumuyor. Yani oturup 2-3 kitap okumuş adam yazmaz bu kodları, istese de yazamaz. Kendimden biliyorum ben de üniversite böyleydim, bu iş okumadan, yazmadan, çizmeden olmaz.

Bazı yerlere bakıyorsun hani bunun kötü bir geliştirici olmakla bile alakası yok, gavur deyimiyle naive olmak gerekiyor. Bir başkasının oturup kodu incelemesi ölüm gibi bir şey, programcının ne yapmak istediğini dahi anlamıyorsun, yorum satırı yok bir şey yok, esneklik yok, niyet açık değil, anti pattern çok fazla var kodlar zaten berbat, bla bla bla onlarca madde sayabilirim size. Hepsinin mühendis olduğunu varsayarsak en kötü ihtimalle bi pragmatic programmer, computer systems, gang 4 design patterns falan okumuş olmaları gerekir, ama gördüğümüz üzere okumamış, okuyup bu haldelerse zaten vah hallerine. Adamlar CPU, RAM nasıl çalışır ondan bile bihaberler, çalışıyor mu çalışıyor mantığıyla hurra diye girişilmiş öğrenci projesine benziyor. Tabii o zamanlar böyle bir oyun ortaya çıkarmışlar canıgönülden tebrik ve takdir edilmesi gereken bir şey, 1 0'dan her zaman iyidir. Benim laf atabileceğim yer programcılıkları, düpedüz C++ ve C bilmeyen insanlar tarafından üretilmiş, normal şartlarda bu tarzda kod ancak pseudo development tarafında yapılır.

Yıllardır duyarız dediğiniz kısım için sorun eleştirenlerin %98'inin halen 25 senelik YMIR düzeyinde bile olamaması, o yüzden yorum yapmayacağım.
 
Bilindiği üzere Metin2 C++ ve python olmak üzere çok geniş bir kod yapısına sahip. Buna ek olarak ymir'in bir ekip olduğunu düşünürsek, bir dosyaya 10 farklı el değmesi muhtemeldir. Bir dosyaya zaman içinde hem çok iyi hem de çok kötü bir yazılımcı müdahale etmiş olması yüksek ihtimal. İnsani faktörler de ayrı bir etki tabi.. Bu sebeple bu tür ithamlar genelde o tarz kişilere yapılıyor, tüm ekibe değil.

Basit bir constructor vakası:
muhtemelen hatuna yetişmek için acele eden bir geliştiricinin hızını alamayıp şöyle bir şey yapması gibi:
C++:
Genişlet Daralt Kopyala
CPythonBackground::CPythonBackground()
{
    m_dwRenderShadowTime=0;
    m_eViewDistanceNum=0; // ✓
    m_eViewDistanceNum=0; // ?
    m_eViewDistanceNum=0; // ???? burada ne yaptığının farkına varmış ve durmuş. ama yine de düzeltilmemiş :D
    ...
    ...
}
1786542111438.webp


ortalama ymir dev
 
zamana göre iyi yazmış adamlar. mesela bahsettiğin std::list kullanılmasının nedeni o vakit list in vektörden daha hızlı olması, poly dosyasında notu görebilirsin.

thecore_memcpy de aynı mantık. adamlar freebsd override kullanmış hızlandırmak için.

geliştiricilere laf atmaya da gerek yok, sen ben ve burada ki tüm geliştiriciler gelse o adamların %1 etmeyiz onu söyleyeyim. el altında stackoverflow, ai, static analyzer ve modern cpp varken konuşmak rahattır.
 
zamana göre iyi yazmış adamlar. mesela bahsettiğin std::list kullanılmasının nedeni o vakit list in vektörden daha hızlı olması, poly dosyasında notu görebilirsin.

thecore_memcpy de aynı mantık. adamlar freebsd override kullanmış hızlandırmak için.

geliştiricilere laf atmaya da gerek yok, sen ben ve burada ki tüm geliştiriciler gelse o adamların %1 etmeyiz onu söyleyeyim. el altında stackoverflow, ai, static analyzer ve modern cpp varken konuşmak rahattır.
Dosyaların sızma mevzusu benim bildiğim akdarıyla 2014 yılına ait, hadi kodu yine 2002-2003'de yazdılar ve 10 yıl boyunca hiç dokunmadılar diyelim(zaten dokundularsa sorun çok daha büyük).



Zamana göre iyi yazmışlar demeniz... Bu sadece ilk geliştirilmeye başlandığı an için geçerli olabilir, sızan dosyalara göre mantıken 2013 yılına kadar aktif geliştirme görmüş olması gerekir, bence ilk soru "on yıl boyunca kimse mi düşünmedi?" olmalıydı.

Sizin bakış açınıza göre o zamanlar kaynak da mı yoktu ki bilgisizliklerini haklı çıkaralım? O zamanlarda yazılmış benim de okumuş olduğum Bjarne, Sutter, Andrei gibi isimlerin çok kaliteli kitapları var, ben 202x'de bunları alıp okuduysam onlar okumadıysa sorun biraz onların gibi. 2010'lu yıllar için bu konuda yorum dahi yapmak istemiyorum.

Neyse;

Poly implemetasyonuna baktım, n çok küçük, stream 50 eleman olsa ne yazar her şey zaten L1'de(zaten sanırsam sonradan fark etmiş olacak ki vector + reserve tercih etmiş). std::list daha hızlı demiş comment olarak, peki;

ölçmüş mü?
ölçüm senaryosu neymiş?
madem std::list daha hızlıymış neden vector tercih etmiş?

adam yazdığı yorumda kendiyle çelişiyor, tahminimce "boşver ya aman" diyip bırakmıştır.

Poly'de implementasyonda zaten çok fazla reallocation var, yaşadığı sorun büyük ihtimalle ondandır. List'in construct vs. maliyeti yazıldığı yıldan bağımsız olarak vectorden az olamaz, bknz. veri yapıları. zaten iterasyon farkı ölçülecek büyüklükte değil, gözüne çarpan şey allocationdır iterasyon değil.

Bunları düşünmeden "adam yazmış!" demek biraz naif bir yaklaşım oluyor ki ben bu kalitede kod yazan bir programcının ölçtüğü bir şey olduğunu sanmıyorum, hani iki log bastırıp timestamplerine bakarak "bu yavaş yow" denilmiş bile olabilir bu mantıkla.

Tüm bunlara ek olarak Poly'e bakarken gördüm kü "Analyze" fonksiyonunun dönüş türü int ama implementasyonda return true / return false yazılmış :D adam fonksiyonun return typeı ile bile uyum sağlayamıyor.

memcpy... bu mantığa göre benim gibi linux projelerinde çalışanlar POSIX kullandı diye zeka küpü ilan edilmeli. memcpy özelinde boyut sabitse zaten builtin call olacaktır, runtimeda belirlenen durumlarda da libc implementasyonuna mecbursun. Herhangi birinin derleyiciden akıllı olabileceğini sanmıyorum, müsait zamanda açıp o zamanki sürüm dökümanlarına bakacağım ama bilinçli bir tercih olduğunu pek sanmıyorum, gerçekten öyle ise ölçüp tekrar bakalım.

Bu camiada yeri geliyor Linus'u yeri geliyor Bjarne'yi, yeri geliyor birbirimizi eleştiriyoruz, YMIR geliştiricilerini mi ayakta alkışlayacağız?

C++98 olsa dahi burada std::list'in savunulabilir bir yanı yok, hadi dilde move semantics yoktu çok fazla ortadan ekleme silme yapıyorum iterator invalidation istemiyorum gibi bir amaç olsa amenna başım gözüm üstüne ama böyle bir kullanım senaryosu yok, 2000'lerde DS'da mı yoktu? Veri yapıları derside mi almadılar? Boost kullanalım diye düşünmeyi biliyorlar ama.

"Elinizde AI ve static analyzer varken..." yorumunda hakkın var ama bu avuntu sadece o dönemde bilinemeyecek şeyler için geçerli. Yukarıda saydıklarımın hiçbiri o kategoride değil ki üzerine konuştuğumuz koskaca projenin sadece tek dosyasına bakarak söylenebileceklerin belki çeyreği. Genele doğru gidersek zaten bu işin sonu yok. Herkes kendini bir kategoriye koyabilir, ben bu kategoriden değilim. Kıyaslamanız sizinle ilgili, benim adıma konuşmayın.
 
Dosyaların sızma mevzusu benim bildiğim akdarıyla 2014 yılına ait, hadi kodu yine 2002-2003'de yazdılar ve 10 yıl boyunca hiç dokunmadılar diyelim(zaten dokundularsa sorun çok daha büyük).



Zamana göre iyi yazmışlar demeniz... Bu sadece ilk geliştirilmeye başlandığı an için geçerli olabilir, sızan dosyalara göre mantıken 2013 yılına kadar aktif geliştirme görmüş olması gerekir, bence ilk soru "on yıl boyunca kimse mi düşünmedi?" olmalıydı.

Sizin bakış açınıza göre o zamanlar kaynak da mı yoktu ki bilgisizliklerini haklı çıkaralım? O zamanlarda yazılmış benim de okumuş olduğum Bjarne, Sutter, Andrei gibi isimlerin çok kaliteli kitapları var, ben 202x'de bunları alıp okuduysam onlar okumadıysa sorun biraz onların gibi. 2010'lu yıllar için bu konuda yorum dahi yapmak istemiyorum.

Neyse;

Poly implemetasyonuna baktım, n çok küçük, stream 50 eleman olsa ne yazar her şey zaten L1'de(zaten sanırsam sonradan fark etmiş olacak ki vector + reserve tercih etmiş). std::list daha hızlı demiş comment olarak, peki;

ölçmüş mü?
ölçüm senaryosu neymiş?
madem std::list daha hızlıymış neden vector tercih etmiş?

adam yazdığı yorumda kendiyle çelişiyor, tahminimce "boşver ya aman" diyip bırakmıştır.

Poly'de implementasyonda zaten çok fazla reallocation var, yaşadığı sorun büyük ihtimalle ondandır. List'in construct vs. maliyeti yazıldığı yıldan bağımsız olarak vectorden az olamaz, bknz. veri yapıları. zaten iterasyon farkı ölçülecek büyüklükte değil, gözüne çarpan şey allocationdır iterasyon değil.

Bunları düşünmeden "adam yazmış!" demek biraz naif bir yaklaşım oluyor ki ben bu kalitede kod yazan bir programcının ölçtüğü bir şey olduğunu sanmıyorum, hani iki log bastırıp timestamplerine bakarak "bu yavaş yow" denilmiş bile olabilir bu mantıkla.

Tüm bunlara ek olarak Poly'e bakarken gördüm kü "Analyze" fonksiyonunun dönüş türü int ama implementasyonda return true / return false yazılmış :D adam fonksiyonun return typeı ile bile uyum sağlayamıyor.

memcpy... bu mantığa göre benim gibi linux projelerinde çalışanlar POSIX kullandı diye zeka küpü ilan edilmeli. memcpy özelinde boyut sabitse zaten builtin call olacaktır, runtimeda belirlenen durumlarda da libc implementasyonuna mecbursun. Herhangi birinin derleyiciden akıllı olabileceğini sanmıyorum, müsait zamanda açıp o zamanki sürüm dökümanlarına bakacağım ama bilinçli bir tercih olduğunu pek sanmıyorum, gerçekten öyle ise ölçüp tekrar bakalım.

Bu camiada yeri geliyor Linus'u yeri geliyor Bjarne'yi, yeri geliyor birbirimizi eleştiriyoruz, YMIR geliştiricilerini mi ayakta alkışlayacağız?

C++98 olsa dahi burada std::list'in savunulabilir bir yanı yok, hadi dilde move semantics yoktu çok fazla ortadan ekleme silme yapıyorum iterator invalidation istemiyorum gibi bir amaç olsa amenna başım gözüm üstüne ama böyle bir kullanım senaryosu yok, 2000'lerde DS'da mı yoktu? Veri yapıları derside mi almadılar? Boost kullanalım diye düşünmeyi biliyorlar ama.

"Elinizde AI ve static analyzer varken..." yorumunda hakkın var ama bu avuntu sadece o dönemde bilinemeyecek şeyler için geçerli. Yukarıda saydıklarımın hiçbiri o kategoride değil ki üzerine konuştuğumuz koskaca projenin sadece tek dosyasına bakarak söylenebileceklerin belki çeyreği. Genele doğru gidersek zaten bu işin sonu yok. Herkes kendini bir kategoriye koyabilir, ben bu kategoriden değilim. Kıyaslamanız sizinle ilgili, benim adıma konuşmayın.
martysama değil de kraizy inceleseydin memcpy.h da neden yavaş olduğuna dair 3 paragraf yazı olduğunu görürdün.

bu arada yine kraizy inceleseydin dev versiyonunda smart pointer kullanıldığını yani sourcede yazılmış olanların değil de yeni yazılanların modern cpp kullandığını da görürdün.
 
martysama değil de kraizy inceleseydin memcpy.h da neden yavaş olduğuna dair 3 paragraf yazı olduğunu görürdün.

bu arada yine kraizy inceleseydin dev versiyonunda smart pointer kullanıldığını yani sourcede yazılmış olanların değil de yeni yazılanların modern cpp kullandığını da görürdün.
Genelde değişen bir şey yok, bir iki noktada iyi bir şey yapmış olsunlar hadi sizi mi kıracağız
 
Genelde değişen bir şey yok, bir iki noktada iyi bir şey yapmış olsunlar hadi sizi mi kıracağız
Bir iki noktada iyi birşey yapmış olsunlar: Zamanında dünyada en çok oynanan ilk 3-5 mmorpg oyunundan birini yapmış adamlar hata açık arayana kusursuz iş elbet yokta bu yorumda biraz ilginçmiş. Keyifli forumlar xD

Bilindiği üzere Metin2 C++ ve python olmak üzere çok geniş bir kod yapısına sahip. Buna ek olarak ymir'in bir ekip olduğunu düşünürsek, bir dosyaya 10 farklı el değmesi muhtemeldir. Bir dosyaya zaman içinde hem çok iyi hem de çok kötü bir yazılımcı müdahale etmiş olması yüksek ihtimal. İnsani faktörler de ayrı bir etki tabi.. Bu sebeple bu tür ithamlar genelde o tarz kişilere yapılıyor, tüm ekibe değil.

Basit bir constructor vakası:
muhtemelen hatuna yetişmek için acele eden bir geliştiricinin hızını alamayıp şöyle bir şey yapması gibi:
C++:
Genişlet Daralt Kopyala
CPythonBackground::CPythonBackground()
{
    m_dwRenderShadowTime=0;
    m_eViewDistanceNum=0; // ✓
    m_eViewDistanceNum=0; // ?
    m_eViewDistanceNum=0; // ???? burada ne yaptığının farkına varmış ve durmuş. ama yine de düzeltilmemiş :D
    ...
    ...
}
E tabi orası öyle birçok yerde bu kodların neden böyle yazıldığını sorgulayan yorum satırları vardı kendi içlerindede bu olayı yaşamışlar yani. Ymir geliştiricileri kötü geliştiricilerdi diyip genellemek ve kod cımbızlayıp bunu genel argüman olarak sunmak komik geliyor
 
Dosyaların sızma mevzusu benim bildiğim akdarıyla 2014 yılına ait, hadi kodu yine 2002-2003'de yazdılar ve 10 yıl boyunca hiç dokunmadılar diyelim(zaten dokundularsa sorun çok daha büyük).



Zamana göre iyi yazmışlar demeniz... Bu sadece ilk geliştirilmeye başlandığı an için geçerli olabilir, sızan dosyalara göre mantıken 2013 yılına kadar aktif geliştirme görmüş olması gerekir, bence ilk soru "on yıl boyunca kimse mi düşünmedi?" olmalıydı.

Sizin bakış açınıza göre o zamanlar kaynak da mı yoktu ki bilgisizliklerini haklı çıkaralım? O zamanlarda yazılmış benim de okumuş olduğum Bjarne, Sutter, Andrei gibi isimlerin çok kaliteli kitapları var, ben 202x'de bunları alıp okuduysam onlar okumadıysa sorun biraz onların gibi. 2010'lu yıllar için bu konuda yorum dahi yapmak istemiyorum.

Neyse;

Poly implemetasyonuna baktım, n çok küçük, stream 50 eleman olsa ne yazar her şey zaten L1'de(zaten sanırsam sonradan fark etmiş olacak ki vector + reserve tercih etmiş). std::list daha hızlı demiş comment olarak, peki;

ölçmüş mü?
ölçüm senaryosu neymiş?
madem std::list daha hızlıymış neden vector tercih etmiş?

adam yazdığı yorumda kendiyle çelişiyor, tahminimce "boşver ya aman" diyip bırakmıştır.

Poly'de implementasyonda zaten çok fazla reallocation var, yaşadığı sorun büyük ihtimalle ondandır. List'in construct vs. maliyeti yazıldığı yıldan bağımsız olarak vectorden az olamaz, bknz. veri yapıları. zaten iterasyon farkı ölçülecek büyüklükte değil, gözüne çarpan şey allocationdır iterasyon değil.

Bunları düşünmeden "adam yazmış!" demek biraz naif bir yaklaşım oluyor ki ben bu kalitede kod yazan bir programcının ölçtüğü bir şey olduğunu sanmıyorum, hani iki log bastırıp timestamplerine bakarak "bu yavaş yow" denilmiş bile olabilir bu mantıkla.

Tüm bunlara ek olarak Poly'e bakarken gördüm kü "Analyze" fonksiyonunun dönüş türü int ama implementasyonda return true / return false yazılmış :D adam fonksiyonun return typeı ile bile uyum sağlayamıyor.

memcpy... bu mantığa göre benim gibi linux projelerinde çalışanlar POSIX kullandı diye zeka küpü ilan edilmeli. memcpy özelinde boyut sabitse zaten builtin call olacaktır, runtimeda belirlenen durumlarda da libc implementasyonuna mecbursun. Herhangi birinin derleyiciden akıllı olabileceğini sanmıyorum, müsait zamanda açıp o zamanki sürüm dökümanlarına bakacağım ama bilinçli bir tercih olduğunu pek sanmıyorum, gerçekten öyle ise ölçüp tekrar bakalım.

Bu camiada yeri geliyor Linus'u yeri geliyor Bjarne'yi, yeri geliyor birbirimizi eleştiriyoruz, YMIR geliştiricilerini mi ayakta alkışlayacağız?

C++98 olsa dahi burada std::list'in savunulabilir bir yanı yok, hadi dilde move semantics yoktu çok fazla ortadan ekleme silme yapıyorum iterator invalidation istemiyorum gibi bir amaç olsa amenna başım gözüm üstüne ama böyle bir kullanım senaryosu yok, 2000'lerde DS'da mı yoktu? Veri yapıları derside mi almadılar? Boost kullanalım diye düşünmeyi biliyorlar ama.

"Elinizde AI ve static analyzer varken..." yorumunda hakkın var ama bu avuntu sadece o dönemde bilinemeyecek şeyler için geçerli. Yukarıda saydıklarımın hiçbiri o kategoride değil ki üzerine konuştuğumuz koskaca projenin sadece tek dosyasına bakarak söylenebileceklerin belki çeyreği. Genele doğru gidersek zaten bu işin sonu yok. Herkes kendini bir kategoriye koyabilir, ben bu kategoriden değilim. Kıyaslamanız sizinle ilgili, benim adıma konuşmayın.

bazı şeyleri zamanına göre değerlendirmek gerekir, örneğin;

"Tüm bunlara ek olarak Poly'e bakarken gördüm kü "Analyze" fonksiyonunun dönüş türü int ama implementasyonda return true / return false yazılmış :D adam fonksiyonun return typeı ile bile uyum sağlayamıyor."

server libleri temel olarak C ile hazırlanmış ancak sonrada C++'a çevrilmiş, bool tanımlamalarının olmaması ya da custom tanımlanma kullanılması olağan, ek olarak dönemine göre c++98'de integral promotion ile bool > int'e dönüştürülür, direkt standart task dökümanlarında bile mevcuttur, hatta aynı dönüşüm SetVar içerisindede var yani kendi içerisinde tutarlı bir kullanım ancak burada sorun zaman içerisinde mevcut yapıya ve geliştirme ortamına uyumlu hale getirmemeleri.

diğer bir konu ise memcpy;

"memcpy... bu mantığa göre benim gibi linux projelerinde çalışanlar POSIX kullandı diye zeka küpü ilan edilmeli. memcpy özelinde boyut sabitse zaten builtin call olacaktır, runtimeda belirlenen durumlarda da libc implementasyonuna mecbursun. Herhangi birinin derleyiciden akıllı olabileceğini sanmıyorum,"

pentium işlemcilerin bulunduğu dönemlerden bahsediyoruz, ki bunlar bile geliştiricilerin erişebileceği donanımlar standart ev kullanıcısı ya da internet kafeler daha vasat haldedir, o dönemdeki donanımlara uygun bir şekilde ve aynı zamanda o dönemdeki primitif libc temellerine göre custom optimizasyon yapılması(yapılmaya çalışılması) çokta saçma bir davranış değildir.

2009'da derlenmiş ilk leaklenen game dosyalarından birini inceledim, 4 farklı memcpy kullanılmış ve kendi içerisindede bir mikro benchmark mevcut;
Kod:
Genişlet Daralt Kopyala
void thecore_find_best_memcpy()
{
  void *v0; // edi
  char *v1; // esi
  int j; // ebx
  int v3; // eax
  char *v4; // edx
  unsigned __int64 v5; // rax
  unsigned __int64 v6; // rax
  __int64 v7; // [esp+Ch] [ebp-3Ch]
  int v8; // [esp+1Ch] [ebp-2Ch]
  unsigned __int64 v9; // [esp+20h] [ebp-28h]
  int best; // [esp+28h] [ebp-20h]
  int i; // [esp+2Ch] [ebp-1Ch]
  unsigned __int64 t; // [esp+30h] [ebp-18h]

  best = 0;
  v0 = calloc(1u, 0xFA0000u);
  if ( v0 )
  {
    v1 = (char *)calloc(1u, 0xFA0000u);
    if ( v1 )
    {
      sys_log(1, v1, "memcpy: Benchmarking memcpy methods (smaller is better):");
      memcpy(v0, v1, 0xFA0000u);
      memcpy(v1, v0, 0xFA0000u);
      i = 1;
      if ( memcpy_method[1].name )
      {
        v8 = 1;
        do
        {
          v9 = __rdtsc();
          for ( j = 0; j <= 1999; ++j )
          {
            v3 = j << 13;
            v4 = &v1[0x2000 * j];
            (*(void (__cdecl **)(int, char *, int))(v8 * 20 + 136580580))((int)v0 + v3, v4, 0x2000);
          }
          v5 = __rdtsc();
          t = v5 - v9;
          v7 = v5 - v9;
          *(_DWORD *)(v8 * 20 + 136580584) = v5 - v9;
          LODWORD(v5) = memcpy_method[v8].name;
          *(_DWORD *)(v8 * 20 + 136580588) = HIDWORD(t);
          sys_log(1, v1, "memcpy: \t%s : %lld", (_DWORD)v5, v7);
          if ( !best
            || (LODWORD(v6) = memcpy_method[best].time, HIDWORD(v6) = HIDWORD(memcpy_method[best].time), t < v6) )
          {
            best = i;
          }
          ++v8;
          ++i;
        }
        while ( memcpy_method[v8].name );
        if ( best )
        {
          sys_log(1, v1, "memcpy: using %s", memcpy_method[best].name);
          thecore_memcpy = memcpy_method[best].function;
        }
      }
      free(v0);
      free(v1);
    }
    else
    {
      free(v0);
    }
  }
}

Kod:
Genişlet Daralt Kopyala
.data:08240DE0 ; struct {char *name;void *(*function)(void *, const void *, size_t);unsigned __int64 time;unsigned int cpu_require;} memcpy_method[6]
.data:08240DE0 memcpy_method   $5F79C1FA8CEB16CD71D5BA6F773D166E <0, 0, 0, 0>
.data:08240DE0                                         ; DATA XREF: thecore_find_best_memcpy+11B↑r
.data:08240DE0                                         ; thecore_find_best_memcpy+174↑r ...
.data:08240DF4                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aGlibcMemcpy, \ ; "glibc memcpy()"
.data:08240DF4                                                    offset _memcpy, 0, 0>
.data:08240E08                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aMmxextOptimize, \ ; "MMXEXT optimized memcpy()"
.data:08240E08                                                    offset mmx2_memcpy, 0, 0>
.data:08240E1C                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aMmxOptimizedMe, \ ; "MMX optimized memcpy()"
.data:08240E1C                                                    offset mmx_memcpy, 0, 0>
.data:08240E30                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aLinuxKernelMem, \ ; "linux kernel memcpy()"
.data:08240E30                                                    offset linux_kernel_memcpy, 0, 0>
.data:08240E44                 $5F79C1FA8CEB16CD71D5BA6F773D166E <0>
.data:08240E58                 public thecore_memcpy
.data:08240E58 ; void *(*thecore_memcpy)(void *, const void *, size_t)
.data:08240E58 thecore_memcpy  dd offset _memcpy       ; DATA XREF: exchange_packet(CHARACTER *,uchar,bool,ulong,ulong,ulong,void *)+9D↑r
.data:08240E58                                         ; exchange_packet(CHARACTER *,uchar,bool,ulong,ulong,ulong,void *)+B9↑r ...
.data:08240E5C                 align 10h
.data:08240E60                 public KStbl

glibc kullanılan mevcuttaki standart memcpy, diğerleri ise custom implementasyonlar.

Kod:
Genişlet Daralt Kopyala
char *__cdecl linux_kernel_memcpy(char *to, char *a2, unsigned int a3)
{
  char *v5; // ebx
  char *v7; // edi
  char *v8; // esi

  v5 = to;
  if ( a3 > 3 )
  {
    qmemcpy(to, a2, 4 * (a3 >> 2));
    v8 = &a2[4 * (a3 >> 2)];
    v7 = &to[4 * (a3 >> 2)];
    if ( (a3 & 2) != 0 )
    {
      *(_WORD *)v7 = *(_WORD *)v8;
      v8 += 2;
      v7 += 2;
    }
    if ( (a3 & 1) != 0 )
      *v7 = *v8;
  }
  else
  {
    qmemcpy(to, a2, a3);
    return &to[a3];
  }
  return v5;
}


_QWORD *__cdecl mmx_memcpy(_QWORD *a1, _QWORD *a2, unsigned int a3)
{
  unsigned int v6; // edx
  _QWORD *v7; // edi
  _QWORD *v8; // esi
  unsigned int v9; // eax
  unsigned int v10; // eax
  __int64 v11; // mm1
  __int64 v12; // mm2
  __int64 v13; // mm3
  __int64 v14; // mm4
  __int64 v15; // mm5
  __int64 v16; // mm6
  __int64 v17; // mm7
  _WORD *v19; // edi
  _WORD *v20; // esi

  v6 = a3;
  v7 = a1;
  v8 = a2;
  if ( a3 > 0x7FF )
  {
    if ( ((unsigned __int8)a1 & 7) != 0 )
    {
      v9 = 8 - ((unsigned __int8)a1 & 7);
      v6 = a3 - v9;
      qmemcpy(a1, a2, v9);
      v8 = (_QWORD *)((char *)a2 + v9);
      v7 = (_QWORD *)((char *)a1 + v9);
    }
    v10 = v6 >> 6;
    for ( v6 &= 0x3Fu; v10; --v10 )
    {
      v11 = v8[1];
      v12 = v8[2];
      v13 = v8[3];
      v14 = v8[4];
      v15 = v8[5];
      v16 = v8[6];
      v17 = v8[7];
      *v7 = *v8;
      v7[1] = v11;
      v7[2] = v12;
      v7[3] = v13;
      v7[4] = v14;
      v7[5] = v15;
      v7[6] = v16;
      v7[7] = v17;
      v8 += 8;
      v7 += 8;
    }
    _m_empty();
  }
  if ( v6 )
  {
    if ( v6 > 3 )
    {
      qmemcpy(v7, v8, 4 * (v6 >> 2));
      v20 = (_WORD *)v8 + 2 * (v6 >> 2);
      v19 = (_WORD *)v7 + 2 * (v6 >> 2);
      if ( (v6 & 2) != 0 )
        *v19++ = *v20++;
      if ( (v6 & 1) != 0 )
        *(_BYTE *)v19 = *(_BYTE *)v20;
    }
    else
    {
      qmemcpy(v7, v8, v6);
    }
  }
  return a1;
}


__m64 *__cdecl mmx2_memcpy(__m64 *a1, const char *a2, unsigned int a3)
{
  unsigned int v6; // edx
  __m64 *v7; // edi
  __m64 *v8; // esi
  unsigned int v10; // eax
  unsigned int v11; // eax
  __m64 v12; // mm1
  __m64 v13; // mm2
  __m64 v14; // mm3
  __m64 v15; // mm4
  __m64 v16; // mm5
  __m64 v17; // mm6
  __m64 v18; // mm7
  _WORD *v20; // edi
  _WORD *v21; // esi

  v6 = a3;
  v7 = a1;
  v8 = (__m64 *)a2;
  _mm_prefetch(a2, 0);
  _mm_prefetch(a2 + 64, 0);
  _mm_prefetch((const char *)&v8[16], 0);
  _mm_prefetch((const char *)&v8[24], 0);
  _mm_prefetch((const char *)&v8[32], 0);
  if ( v6 > 0x3F )
  {
    if ( ((unsigned __int8)a1 & 7) != 0 )
    {
      v10 = 8 - ((unsigned __int8)a1 & 7);
      v6 = a3 - v10;
      qmemcpy(a1, a2, v10);
      v8 = (__m64 *)&a2[v10];
      v7 = (__m64 *)((char *)a1 + v10);
    }
    v11 = v6 >> 6;
    for ( v6 &= 0x3Fu; v11; --v11 )
    {
      _mm_prefetch((const char *)&v8[40], 0);
      v12 = v8[1];
      v13 = v8[2];
      v14 = v8[3];
      v15 = v8[4];
      v16 = v8[5];
      v17 = v8[6];
      v18 = v8[7];
      _mm_stream_pi(v7, (__m64)v8->m64_u64);
      _mm_stream_pi(v7 + 1, v12);
      _mm_stream_pi(v7 + 2, v13);
      _mm_stream_pi(v7 + 3, v14);
      _mm_stream_pi(v7 + 4, v15);
      _mm_stream_pi(v7 + 5, v16);
      _mm_stream_pi(v7 + 6, v17);
      _mm_stream_pi(v7 + 7, v18);
      v8 += 8;
      v7 += 8;
    }
    _mm_sfence();
    _m_empty();
  }
  if ( v6 )
  {
    if ( v6 > 3 )
    {
      qmemcpy(v7, v8, 4 * (v6 >> 2));
      v21 = (_WORD *)v8 + 2 * (v6 >> 2);
      v20 = (_WORD *)v7 + 2 * (v6 >> 2);
      if ( (v6 & 2) != 0 )
        *v20++ = *v21++;
      if ( (v6 & 1) != 0 )
        *(_BYTE *)v20 = *(_BYTE *)v21;
    }
    else
    {
      qmemcpy(v7, v8, v6);
    }
  }
  return a1;
}


// attributes: thunk
void *memcpy(void *dest, const void *src, size_t n)
{
  return memcpy(dest, src, n);
}


ai ile game binarysinin sürümünü ve derleyici sürümünü buldum
- Derleyici: GNU GCC
- Sürüm: 3.3.3
- FreeBSD derlemesi: GNU C 3.3.3 [FreeBSD] 20031106
- Hedef: 32-bit i386
- Kaynak dili: ANSI C
- Debug formatı: DWARF 2
- Hedef sistem: FreeBSD 5.2.1

ve glibc'nin memcpy implementasyonunuda bulup( ) kullandıkları metodu 1:1 taklit eden bir benchmark yazdırdım

Rj3EwCJ.png


özet: duruma göre custom memcpy daha kazançlı olabiliyor.

tabi '97 işlemcisi ile 2024 işlemcisi performansını karşılaştırmak mantıklı değil ancak genel bir fikir verecektir.

meraklısına projeyide ekledim.
 

Dosya Eklentileri

bazı şeyleri zamanına göre değerlendirmek gerekir, örneğin;

"Tüm bunlara ek olarak Poly'e bakarken gördüm kü "Analyze" fonksiyonunun dönüş türü int ama implementasyonda return true / return false yazılmış :D adam fonksiyonun return typeı ile bile uyum sağlayamıyor."

server libleri temel olarak C ile hazırlanmış ancak sonrada C++'a çevrilmiş, bool tanımlamalarının olmaması ya da custom tanımlanma kullanılması olağan, ek olarak dönemine göre c++98'de integral promotion ile bool > int'e dönüştürülür, direkt standart task dökümanlarında bile mevcuttur, hatta aynı dönüşüm SetVar içerisindede var yani kendi içerisinde tutarlı bir kullanım ancak burada sorun zaman içerisinde mevcut yapıya ve geliştirme ortamına uyumlu hale getirmemeleri.

diğer bir konu ise memcpy;

"memcpy... bu mantığa göre benim gibi linux projelerinde çalışanlar POSIX kullandı diye zeka küpü ilan edilmeli. memcpy özelinde boyut sabitse zaten builtin call olacaktır, runtimeda belirlenen durumlarda da libc implementasyonuna mecbursun. Herhangi birinin derleyiciden akıllı olabileceğini sanmıyorum,"

pentium işlemcilerin bulunduğu dönemlerden bahsediyoruz, ki bunlar bile geliştiricilerin erişebileceği donanımlar standart ev kullanıcısı ya da internet kafeler daha vasat haldedir, o dönemdeki donanımlara uygun bir şekilde ve aynı zamanda o dönemdeki primitif libc temellerine göre custom optimizasyon yapılması(yapılmaya çalışılması) çokta saçma bir davranış değildir.

2009'da derlenmiş ilk leaklenen game dosyalarından birini inceledim, 4 farklı memcpy kullanılmış ve kendi içerisindede bir mikro benchmark mevcut;
Kod:
Genişlet Daralt Kopyala
void thecore_find_best_memcpy()
{
  void *v0; // edi
  char *v1; // esi
  int j; // ebx
  int v3; // eax
  char *v4; // edx
  unsigned __int64 v5; // rax
  unsigned __int64 v6; // rax
  __int64 v7; // [esp+Ch] [ebp-3Ch]
  int v8; // [esp+1Ch] [ebp-2Ch]
  unsigned __int64 v9; // [esp+20h] [ebp-28h]
  int best; // [esp+28h] [ebp-20h]
  int i; // [esp+2Ch] [ebp-1Ch]
  unsigned __int64 t; // [esp+30h] [ebp-18h]

  best = 0;
  v0 = calloc(1u, 0xFA0000u);
  if ( v0 )
  {
    v1 = (char *)calloc(1u, 0xFA0000u);
    if ( v1 )
    {
      sys_log(1, v1, "memcpy: Benchmarking memcpy methods (smaller is better):");
      memcpy(v0, v1, 0xFA0000u);
      memcpy(v1, v0, 0xFA0000u);
      i = 1;
      if ( memcpy_method[1].name )
      {
        v8 = 1;
        do
        {
          v9 = __rdtsc();
          for ( j = 0; j <= 1999; ++j )
          {
            v3 = j << 13;
            v4 = &v1[0x2000 * j];
            (*(void (__cdecl **)(int, char *, int))(v8 * 20 + 136580580))((int)v0 + v3, v4, 0x2000);
          }
          v5 = __rdtsc();
          t = v5 - v9;
          v7 = v5 - v9;
          *(_DWORD *)(v8 * 20 + 136580584) = v5 - v9;
          LODWORD(v5) = memcpy_method[v8].name;
          *(_DWORD *)(v8 * 20 + 136580588) = HIDWORD(t);
          sys_log(1, v1, "memcpy: \t%s : %lld", (_DWORD)v5, v7);
          if ( !best
            || (LODWORD(v6) = memcpy_method[best].time, HIDWORD(v6) = HIDWORD(memcpy_method[best].time), t < v6) )
          {
            best = i;
          }
          ++v8;
          ++i;
        }
        while ( memcpy_method[v8].name );
        if ( best )
        {
          sys_log(1, v1, "memcpy: using %s", memcpy_method[best].name);
          thecore_memcpy = memcpy_method[best].function;
        }
      }
      free(v0);
      free(v1);
    }
    else
    {
      free(v0);
    }
  }
}

Kod:
Genişlet Daralt Kopyala
.data:08240DE0 ; struct {char *name;void *(*function)(void *, const void *, size_t);unsigned __int64 time;unsigned int cpu_require;} memcpy_method[6]
.data:08240DE0 memcpy_method   $5F79C1FA8CEB16CD71D5BA6F773D166E <0, 0, 0, 0>
.data:08240DE0                                         ; DATA XREF: thecore_find_best_memcpy+11B↑r
.data:08240DE0                                         ; thecore_find_best_memcpy+174↑r ...
.data:08240DF4                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aGlibcMemcpy, \ ; "glibc memcpy()"
.data:08240DF4                                                    offset _memcpy, 0, 0>
.data:08240E08                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aMmxextOptimize, \ ; "MMXEXT optimized memcpy()"
.data:08240E08                                                    offset mmx2_memcpy, 0, 0>
.data:08240E1C                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aMmxOptimizedMe, \ ; "MMX optimized memcpy()"
.data:08240E1C                                                    offset mmx_memcpy, 0, 0>
.data:08240E30                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aLinuxKernelMem, \ ; "linux kernel memcpy()"
.data:08240E30                                                    offset linux_kernel_memcpy, 0, 0>
.data:08240E44                 $5F79C1FA8CEB16CD71D5BA6F773D166E <0>
.data:08240E58                 public thecore_memcpy
.data:08240E58 ; void *(*thecore_memcpy)(void *, const void *, size_t)
.data:08240E58 thecore_memcpy  dd offset _memcpy       ; DATA XREF: exchange_packet(CHARACTER *,uchar,bool,ulong,ulong,ulong,void *)+9D↑r
.data:08240E58                                         ; exchange_packet(CHARACTER *,uchar,bool,ulong,ulong,ulong,void *)+B9↑r ...
.data:08240E5C                 align 10h
.data:08240E60                 public KStbl

glibc kullanılan mevcuttaki standart memcpy, diğerleri ise custom implementasyonlar.

Kod:
Genişlet Daralt Kopyala
char *__cdecl linux_kernel_memcpy(char *to, char *a2, unsigned int a3)
{
  char *v5; // ebx
  char *v7; // edi
  char *v8; // esi

  v5 = to;
  if ( a3 > 3 )
  {
    qmemcpy(to, a2, 4 * (a3 >> 2));
    v8 = &a2[4 * (a3 >> 2)];
    v7 = &to[4 * (a3 >> 2)];
    if ( (a3 & 2) != 0 )
    {
      *(_WORD *)v7 = *(_WORD *)v8;
      v8 += 2;
      v7 += 2;
    }
    if ( (a3 & 1) != 0 )
      *v7 = *v8;
  }
  else
  {
    qmemcpy(to, a2, a3);
    return &to[a3];
  }
  return v5;
}


_QWORD *__cdecl mmx_memcpy(_QWORD *a1, _QWORD *a2, unsigned int a3)
{
  unsigned int v6; // edx
  _QWORD *v7; // edi
  _QWORD *v8; // esi
  unsigned int v9; // eax
  unsigned int v10; // eax
  __int64 v11; // mm1
  __int64 v12; // mm2
  __int64 v13; // mm3
  __int64 v14; // mm4
  __int64 v15; // mm5
  __int64 v16; // mm6
  __int64 v17; // mm7
  _WORD *v19; // edi
  _WORD *v20; // esi

  v6 = a3;
  v7 = a1;
  v8 = a2;
  if ( a3 > 0x7FF )
  {
    if ( ((unsigned __int8)a1 & 7) != 0 )
    {
      v9 = 8 - ((unsigned __int8)a1 & 7);
      v6 = a3 - v9;
      qmemcpy(a1, a2, v9);
      v8 = (_QWORD *)((char *)a2 + v9);
      v7 = (_QWORD *)((char *)a1 + v9);
    }
    v10 = v6 >> 6;
    for ( v6 &= 0x3Fu; v10; --v10 )
    {
      v11 = v8[1];
      v12 = v8[2];
      v13 = v8[3];
      v14 = v8[4];
      v15 = v8[5];
      v16 = v8[6];
      v17 = v8[7];
      *v7 = *v8;
      v7[1] = v11;
      v7[2] = v12;
      v7[3] = v13;
      v7[4] = v14;
      v7[5] = v15;
      v7[6] = v16;
      v7[7] = v17;
      v8 += 8;
      v7 += 8;
    }
    _m_empty();
  }
  if ( v6 )
  {
    if ( v6 > 3 )
    {
      qmemcpy(v7, v8, 4 * (v6 >> 2));
      v20 = (_WORD *)v8 + 2 * (v6 >> 2);
      v19 = (_WORD *)v7 + 2 * (v6 >> 2);
      if ( (v6 & 2) != 0 )
        *v19++ = *v20++;
      if ( (v6 & 1) != 0 )
        *(_BYTE *)v19 = *(_BYTE *)v20;
    }
    else
    {
      qmemcpy(v7, v8, v6);
    }
  }
  return a1;
}


__m64 *__cdecl mmx2_memcpy(__m64 *a1, const char *a2, unsigned int a3)
{
  unsigned int v6; // edx
  __m64 *v7; // edi
  __m64 *v8; // esi
  unsigned int v10; // eax
  unsigned int v11; // eax
  __m64 v12; // mm1
  __m64 v13; // mm2
  __m64 v14; // mm3
  __m64 v15; // mm4
  __m64 v16; // mm5
  __m64 v17; // mm6
  __m64 v18; // mm7
  _WORD *v20; // edi
  _WORD *v21; // esi

  v6 = a3;
  v7 = a1;
  v8 = (__m64 *)a2;
  _mm_prefetch(a2, 0);
  _mm_prefetch(a2 + 64, 0);
  _mm_prefetch((const char *)&v8[16], 0);
  _mm_prefetch((const char *)&v8[24], 0);
  _mm_prefetch((const char *)&v8[32], 0);
  if ( v6 > 0x3F )
  {
    if ( ((unsigned __int8)a1 & 7) != 0 )
    {
      v10 = 8 - ((unsigned __int8)a1 & 7);
      v6 = a3 - v10;
      qmemcpy(a1, a2, v10);
      v8 = (__m64 *)&a2[v10];
      v7 = (__m64 *)((char *)a1 + v10);
    }
    v11 = v6 >> 6;
    for ( v6 &= 0x3Fu; v11; --v11 )
    {
      _mm_prefetch((const char *)&v8[40], 0);
      v12 = v8[1];
      v13 = v8[2];
      v14 = v8[3];
      v15 = v8[4];
      v16 = v8[5];
      v17 = v8[6];
      v18 = v8[7];
      _mm_stream_pi(v7, (__m64)v8->m64_u64);
      _mm_stream_pi(v7 + 1, v12);
      _mm_stream_pi(v7 + 2, v13);
      _mm_stream_pi(v7 + 3, v14);
      _mm_stream_pi(v7 + 4, v15);
      _mm_stream_pi(v7 + 5, v16);
      _mm_stream_pi(v7 + 6, v17);
      _mm_stream_pi(v7 + 7, v18);
      v8 += 8;
      v7 += 8;
    }
    _mm_sfence();
    _m_empty();
  }
  if ( v6 )
  {
    if ( v6 > 3 )
    {
      qmemcpy(v7, v8, 4 * (v6 >> 2));
      v21 = (_WORD *)v8 + 2 * (v6 >> 2);
      v20 = (_WORD *)v7 + 2 * (v6 >> 2);
      if ( (v6 & 2) != 0 )
        *v20++ = *v21++;
      if ( (v6 & 1) != 0 )
        *(_BYTE *)v20 = *(_BYTE *)v21;
    }
    else
    {
      qmemcpy(v7, v8, v6);
    }
  }
  return a1;
}


// attributes: thunk
void *memcpy(void *dest, const void *src, size_t n)
{
  return memcpy(dest, src, n);
}


ai ile game binarysinin sürümünü ve derleyici sürümünü buldum
- Derleyici: GNU GCC
- Sürüm: 3.3.3
- FreeBSD derlemesi: GNU C 3.3.3 [FreeBSD] 20031106
- Hedef: 32-bit i386
- Kaynak dili: ANSI C
- Debug formatı: DWARF 2
- Hedef sistem: FreeBSD 5.2.1

ve glibc'nin memcpy implementasyonunuda bulup( ) kullandıkları metodu 1:1 taklit eden bir benchmark yazdırdım

Rj3EwCJ.png


özet: duruma göre custom memcpy daha kazançlı olabiliyor.

tabi '97 işlemcisi ile 2024 işlemcisi performansını karşılaştırmak mantıklı değil ancak genel bir fikir verecektir.

meraklısına projeyide ekledim.
kanıtlarla konuşmak en doğru yaklaşım
 
bazı şeyleri zamanına göre değerlendirmek gerekir, örneğin;

"Tüm bunlara ek olarak Poly'e bakarken gördüm kü "Analyze" fonksiyonunun dönüş türü int ama implementasyonda return true / return false yazılmış :D adam fonksiyonun return typeı ile bile uyum sağlayamıyor."

server libleri temel olarak C ile hazırlanmış ancak sonrada C++'a çevrilmiş, bool tanımlamalarının olmaması ya da custom tanımlanma kullanılması olağan, ek olarak dönemine göre c++98'de integral promotion ile bool > int'e dönüştürülür, direkt standart task dökümanlarında bile mevcuttur, hatta aynı dönüşüm SetVar içerisindede var yani kendi içerisinde tutarlı bir kullanım ancak burada sorun zaman içerisinde mevcut yapıya ve geliştirme ortamına uyumlu hale getirmemeleri.

diğer bir konu ise memcpy;

"memcpy... bu mantığa göre benim gibi linux projelerinde çalışanlar POSIX kullandı diye zeka küpü ilan edilmeli. memcpy özelinde boyut sabitse zaten builtin call olacaktır, runtimeda belirlenen durumlarda da libc implementasyonuna mecbursun. Herhangi birinin derleyiciden akıllı olabileceğini sanmıyorum,"

pentium işlemcilerin bulunduğu dönemlerden bahsediyoruz, ki bunlar bile geliştiricilerin erişebileceği donanımlar standart ev kullanıcısı ya da internet kafeler daha vasat haldedir, o dönemdeki donanımlara uygun bir şekilde ve aynı zamanda o dönemdeki primitif libc temellerine göre custom optimizasyon yapılması(yapılmaya çalışılması) çokta saçma bir davranış değildir.

2009'da derlenmiş ilk leaklenen game dosyalarından birini inceledim, 4 farklı memcpy kullanılmış ve kendi içerisindede bir mikro benchmark mevcut;
Kod:
Genişlet Daralt Kopyala
void thecore_find_best_memcpy()
{
  void *v0; // edi
  char *v1; // esi
  int j; // ebx
  int v3; // eax
  char *v4; // edx
  unsigned __int64 v5; // rax
  unsigned __int64 v6; // rax
  __int64 v7; // [esp+Ch] [ebp-3Ch]
  int v8; // [esp+1Ch] [ebp-2Ch]
  unsigned __int64 v9; // [esp+20h] [ebp-28h]
  int best; // [esp+28h] [ebp-20h]
  int i; // [esp+2Ch] [ebp-1Ch]
  unsigned __int64 t; // [esp+30h] [ebp-18h]

  best = 0;
  v0 = calloc(1u, 0xFA0000u);
  if ( v0 )
  {
    v1 = (char *)calloc(1u, 0xFA0000u);
    if ( v1 )
    {
      sys_log(1, v1, "memcpy: Benchmarking memcpy methods (smaller is better):");
      memcpy(v0, v1, 0xFA0000u);
      memcpy(v1, v0, 0xFA0000u);
      i = 1;
      if ( memcpy_method[1].name )
      {
        v8 = 1;
        do
        {
          v9 = __rdtsc();
          for ( j = 0; j <= 1999; ++j )
          {
            v3 = j << 13;
            v4 = &v1[0x2000 * j];
            (*(void (__cdecl **)(int, char *, int))(v8 * 20 + 136580580))((int)v0 + v3, v4, 0x2000);
          }
          v5 = __rdtsc();
          t = v5 - v9;
          v7 = v5 - v9;
          *(_DWORD *)(v8 * 20 + 136580584) = v5 - v9;
          LODWORD(v5) = memcpy_method[v8].name;
          *(_DWORD *)(v8 * 20 + 136580588) = HIDWORD(t);
          sys_log(1, v1, "memcpy: \t%s : %lld", (_DWORD)v5, v7);
          if ( !best
            || (LODWORD(v6) = memcpy_method[best].time, HIDWORD(v6) = HIDWORD(memcpy_method[best].time), t < v6) )
          {
            best = i;
          }
          ++v8;
          ++i;
        }
        while ( memcpy_method[v8].name );
        if ( best )
        {
          sys_log(1, v1, "memcpy: using %s", memcpy_method[best].name);
          thecore_memcpy = memcpy_method[best].function;
        }
      }
      free(v0);
      free(v1);
    }
    else
    {
      free(v0);
    }
  }
}

Kod:
Genişlet Daralt Kopyala
.data:08240DE0 ; struct {char *name;void *(*function)(void *, const void *, size_t);unsigned __int64 time;unsigned int cpu_require;} memcpy_method[6]
.data:08240DE0 memcpy_method   $5F79C1FA8CEB16CD71D5BA6F773D166E <0, 0, 0, 0>
.data:08240DE0                                         ; DATA XREF: thecore_find_best_memcpy+11B↑r
.data:08240DE0                                         ; thecore_find_best_memcpy+174↑r ...
.data:08240DF4                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aGlibcMemcpy, \ ; "glibc memcpy()"
.data:08240DF4                                                    offset _memcpy, 0, 0>
.data:08240E08                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aMmxextOptimize, \ ; "MMXEXT optimized memcpy()"
.data:08240E08                                                    offset mmx2_memcpy, 0, 0>
.data:08240E1C                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aMmxOptimizedMe, \ ; "MMX optimized memcpy()"
.data:08240E1C                                                    offset mmx_memcpy, 0, 0>
.data:08240E30                 $5F79C1FA8CEB16CD71D5BA6F773D166E <offset aLinuxKernelMem, \ ; "linux kernel memcpy()"
.data:08240E30                                                    offset linux_kernel_memcpy, 0, 0>
.data:08240E44                 $5F79C1FA8CEB16CD71D5BA6F773D166E <0>
.data:08240E58                 public thecore_memcpy
.data:08240E58 ; void *(*thecore_memcpy)(void *, const void *, size_t)
.data:08240E58 thecore_memcpy  dd offset _memcpy       ; DATA XREF: exchange_packet(CHARACTER *,uchar,bool,ulong,ulong,ulong,void *)+9D↑r
.data:08240E58                                         ; exchange_packet(CHARACTER *,uchar,bool,ulong,ulong,ulong,void *)+B9↑r ...
.data:08240E5C                 align 10h
.data:08240E60                 public KStbl

glibc kullanılan mevcuttaki standart memcpy, diğerleri ise custom implementasyonlar.

Kod:
Genişlet Daralt Kopyala
char *__cdecl linux_kernel_memcpy(char *to, char *a2, unsigned int a3)
{
  char *v5; // ebx
  char *v7; // edi
  char *v8; // esi

  v5 = to;
  if ( a3 > 3 )
  {
    qmemcpy(to, a2, 4 * (a3 >> 2));
    v8 = &a2[4 * (a3 >> 2)];
    v7 = &to[4 * (a3 >> 2)];
    if ( (a3 & 2) != 0 )
    {
      *(_WORD *)v7 = *(_WORD *)v8;
      v8 += 2;
      v7 += 2;
    }
    if ( (a3 & 1) != 0 )
      *v7 = *v8;
  }
  else
  {
    qmemcpy(to, a2, a3);
    return &to[a3];
  }
  return v5;
}


_QWORD *__cdecl mmx_memcpy(_QWORD *a1, _QWORD *a2, unsigned int a3)
{
  unsigned int v6; // edx
  _QWORD *v7; // edi
  _QWORD *v8; // esi
  unsigned int v9; // eax
  unsigned int v10; // eax
  __int64 v11; // mm1
  __int64 v12; // mm2
  __int64 v13; // mm3
  __int64 v14; // mm4
  __int64 v15; // mm5
  __int64 v16; // mm6
  __int64 v17; // mm7
  _WORD *v19; // edi
  _WORD *v20; // esi

  v6 = a3;
  v7 = a1;
  v8 = a2;
  if ( a3 > 0x7FF )
  {
    if ( ((unsigned __int8)a1 & 7) != 0 )
    {
      v9 = 8 - ((unsigned __int8)a1 & 7);
      v6 = a3 - v9;
      qmemcpy(a1, a2, v9);
      v8 = (_QWORD *)((char *)a2 + v9);
      v7 = (_QWORD *)((char *)a1 + v9);
    }
    v10 = v6 >> 6;
    for ( v6 &= 0x3Fu; v10; --v10 )
    {
      v11 = v8[1];
      v12 = v8[2];
      v13 = v8[3];
      v14 = v8[4];
      v15 = v8[5];
      v16 = v8[6];
      v17 = v8[7];
      *v7 = *v8;
      v7[1] = v11;
      v7[2] = v12;
      v7[3] = v13;
      v7[4] = v14;
      v7[5] = v15;
      v7[6] = v16;
      v7[7] = v17;
      v8 += 8;
      v7 += 8;
    }
    _m_empty();
  }
  if ( v6 )
  {
    if ( v6 > 3 )
    {
      qmemcpy(v7, v8, 4 * (v6 >> 2));
      v20 = (_WORD *)v8 + 2 * (v6 >> 2);
      v19 = (_WORD *)v7 + 2 * (v6 >> 2);
      if ( (v6 & 2) != 0 )
        *v19++ = *v20++;
      if ( (v6 & 1) != 0 )
        *(_BYTE *)v19 = *(_BYTE *)v20;
    }
    else
    {
      qmemcpy(v7, v8, v6);
    }
  }
  return a1;
}


__m64 *__cdecl mmx2_memcpy(__m64 *a1, const char *a2, unsigned int a3)
{
  unsigned int v6; // edx
  __m64 *v7; // edi
  __m64 *v8; // esi
  unsigned int v10; // eax
  unsigned int v11; // eax
  __m64 v12; // mm1
  __m64 v13; // mm2
  __m64 v14; // mm3
  __m64 v15; // mm4
  __m64 v16; // mm5
  __m64 v17; // mm6
  __m64 v18; // mm7
  _WORD *v20; // edi
  _WORD *v21; // esi

  v6 = a3;
  v7 = a1;
  v8 = (__m64 *)a2;
  _mm_prefetch(a2, 0);
  _mm_prefetch(a2 + 64, 0);
  _mm_prefetch((const char *)&v8[16], 0);
  _mm_prefetch((const char *)&v8[24], 0);
  _mm_prefetch((const char *)&v8[32], 0);
  if ( v6 > 0x3F )
  {
    if ( ((unsigned __int8)a1 & 7) != 0 )
    {
      v10 = 8 - ((unsigned __int8)a1 & 7);
      v6 = a3 - v10;
      qmemcpy(a1, a2, v10);
      v8 = (__m64 *)&a2[v10];
      v7 = (__m64 *)((char *)a1 + v10);
    }
    v11 = v6 >> 6;
    for ( v6 &= 0x3Fu; v11; --v11 )
    {
      _mm_prefetch((const char *)&v8[40], 0);
      v12 = v8[1];
      v13 = v8[2];
      v14 = v8[3];
      v15 = v8[4];
      v16 = v8[5];
      v17 = v8[6];
      v18 = v8[7];
      _mm_stream_pi(v7, (__m64)v8->m64_u64);
      _mm_stream_pi(v7 + 1, v12);
      _mm_stream_pi(v7 + 2, v13);
      _mm_stream_pi(v7 + 3, v14);
      _mm_stream_pi(v7 + 4, v15);
      _mm_stream_pi(v7 + 5, v16);
      _mm_stream_pi(v7 + 6, v17);
      _mm_stream_pi(v7 + 7, v18);
      v8 += 8;
      v7 += 8;
    }
    _mm_sfence();
    _m_empty();
  }
  if ( v6 )
  {
    if ( v6 > 3 )
    {
      qmemcpy(v7, v8, 4 * (v6 >> 2));
      v21 = (_WORD *)v8 + 2 * (v6 >> 2);
      v20 = (_WORD *)v7 + 2 * (v6 >> 2);
      if ( (v6 & 2) != 0 )
        *v20++ = *v21++;
      if ( (v6 & 1) != 0 )
        *(_BYTE *)v20 = *(_BYTE *)v21;
    }
    else
    {
      qmemcpy(v7, v8, v6);
    }
  }
  return a1;
}


// attributes: thunk
void *memcpy(void *dest, const void *src, size_t n)
{
  return memcpy(dest, src, n);
}


ai ile game binarysinin sürümünü ve derleyici sürümünü buldum
- Derleyici: GNU GCC
- Sürüm: 3.3.3
- FreeBSD derlemesi: GNU C 3.3.3 [FreeBSD] 20031106
- Hedef: 32-bit i386
- Kaynak dili: ANSI C
- Debug formatı: DWARF 2
- Hedef sistem: FreeBSD 5.2.1

ve glibc'nin memcpy implementasyonunuda bulup( ) kullandıkları metodu 1:1 taklit eden bir benchmark yazdırdım

Rj3EwCJ.png


özet: duruma göre custom memcpy daha kazançlı olabiliyor.

tabi '97 işlemcisi ile 2024 işlemcisi performansını karşılaştırmak mantıklı değil ancak genel bir fikir verecektir.

meraklısına projeyide ekledim.
bunlar c++ programcısının adından daha iyi bilmesi gereken şeyler de kast ettiğim şey integral promotion değil, olay tutarsızlık ve yalan söylenmesi.

olay şu: bir API yazıyorum, kullanıcıya diyorum ki bu algoritma sonucunda integer değer return edeceğim, ama implementasyonda 1 ya da 0 return ediyorum. herhangi bir döküman, yorum vs hiçbir şey de yok. sizce de saçma değil mi? 10 yıl geliştirilmiş prod kodda böyle saçmalık mı olur?

fonksiyon imzası = sözleşme. int dönen bir fonksiyonda dönüş değerinin domaini imzadan anlaşılamaz, {0,1} mi {-1, 0, >0} mı, bir sayaç mı? bunu ya tip söyleyecek ya dokümantasyon. ikisi de yoksa ya kodu okuyup anlamaya çalışacaksınız, ya da tahmin edeceksiniz. sözleşme hiçbir yerde tanımlı değil. ben bir sistem programcısı olarak önce -1/0 ayrımını düşünürüm(POSIX geleneği), bir başkası başka bir şey düşünür, bu bile başlı başına bir problem.

memcpy tarafında attığınızı ASM düzeyinde incelemeye çalışacağım, gözüme çarpan şu:


Kod:
Genişlet Daralt Kopyala
.data:08240DE0 ; struct {char *name; void *(*function)(void *, const void *, size_t);
                       unsigned __int64 time; unsigned int cpu_require;} memcpy_method[6]

<offset aGlibcMemcpy,   offset _memcpy,             0, 0>
<offset aMmxextOptimize, offset mmx2_memcpy,        0, 0>
<offset aMmxOptimizedMe, offset mmx_memcpy,         0, 0>
<offset aLinuxKernelMem, offset linux_kernel_memcpy,0, 0>

alan hepsinde 0 ve fonksiyonda hiç okunmamış, kimse kullanmayacağı alanı tanımlamaz diye düşünüyorum, bakacağım.

neyse, yapay zeka çıktısı doğrudur yanlıştır, kontrol etmeden bir şey diyemem.

keza zaten laf etitğim özellikle memcpy'de şunu yapmışlar bunu yapmışlar değil, codebase geneli. yukarıdaki yorumda söylediğim gibi ölçmeden bir şey diyebilecek durumda değilim, kimse diyemez, emeğiniz için teşekkürler kontrol edilebilir. velhasıl oyunun tasarımı, kod kalitesi, dökümantasyonu, yazılım tasarımı, mimarisi vs. berbat halde, benim düşüncem ve söylendiğim şey bu, dünyadaki en iyi memcpy implementasyonunu kullansa ne olur, sistemin %90'ı berbat haldeyse %10'luk kısım bütünü mükemmel hale getiremez, ev gibi düşünün, arka bahçenizi mükemmelleştirseniz ama eviniz viran olsa dışardan görünen nedir? arka bahçenin evin geneline etkisi nedir?

Bir iki noktada iyi birşey yapmış olsunlar: Zamanında dünyada en çok oynanan ilk 3-5 mmorpg oyunundan birini yapmış adamlar hata açık arayana kusursuz iş elbet yokta bu yorumda biraz ilginçmiş. Keyifli forumlar xD


E tabi orası öyle birçok yerde bu kodların neden böyle yazıldığını sorgulayan yorum satırları vardı kendi içlerindede bu olayı yaşamışlar yani. Ymir geliştiricileri kötü geliştiricilerdi diyip genellemek ve kod cımbızlayıp bunu genel argüman olarak sunmak komik geliyor
şu kraldan çok kralcılık her alanda olmak zorunda mı?

iyiye iyi kötüye kötü derim olay bu kadar basit. teknik konuşabilecekseniz üzerine konuşalım, yoksa "adamlar yapmış yav", "çok oynanıyor yav" gibi argümanlarla lütfen bana gelmeyin, laf kalabalığından başka bir şeye yaramaz iki tarafın da yaptığı.

oturup oyun dosyasını tarayıp açık arayacak sonra burda konu açıp egomu tatmin etmeye çalışacak değilim, farklı bir forumda optimizasyon paylaşımı gördüm, işim yokken ne yapmış diye baktım ve bunu fark ettim paylaşayım dedim. metin2'ye laf atmak benim geçim kaynağım değil bir şey değil, gördüğümü paylaştım yoksa kim ne yapmış banane.
 
Geri
Üst