Tek bir iş parçacığı birden çok çekirdekte nasıl çalışır?


61

Tek bir iş parçacığının birden fazla çekirdek üzerinde nasıl çalıştığını yüksek düzeyde anlamaya çalışıyorum. Aşağıda benim en iyi anlayışım. Yine de doğru olduğuna inanmıyorum.

Hyper-threading okumamdan yola çıkarak , İşletim Sisteminin tüm konuların talimatlarını birbirlerini beklemeyecek şekilde düzenlediği anlaşılıyor. Ardından CPU'nun ön ucu ayrıca her bir çekirdeğe bir iş parçacığı dağıtarak bu talimatları düzenler ve her iş parçasından bağımsız komutları açık döngüler arasında dağıtır.

Dolayısıyla, yalnızca tek bir iş parçacığı varsa, işletim sistemi herhangi bir optimizasyon yapmaz. Bununla birlikte, CPU'nun ön ucu, her bir çekirdek arasında bağımsız komut setleri dağıtacaktır.

Https://stackoverflow.com/a/15936270’e göre , belirli bir programlama dili az çok konu yaratabilir, ancak o konu ile ne yapılacağının belirlenmesinde önemsizdir. İşletim sistemi ve CPU bunu yönetir, bu nedenle kullanılan programlama diline bakmaksızın gerçekleşir.

görüntü tanımını buraya girin

Sadece netleştirmek için, tek bir çekirdekte birden fazla iş parçacığı çalıştırmak değil, birden çok çekirdekten geçen tek bir iş parçacığı hakkında soruyorum.

Özetimin nesi var? Bir iş parçacığının talimatları nerede ve nasıl birden fazla çekirdek arasında bölünür? Programlama dili önemli mi? Bunun geniş bir konu olduğunu biliyorum; Bunun üst düzeyde anlaşılmasını umuyorum.


6
Tek bir yazılım iş parçacığı için bir dizi talimat, birçok çekirdeğin üzerinde çalışabilir, ancak bir kerede çalışmayabilir.
Kroltan

1
Yazılım dizilerini (işletim sistemi zamanlayıcısını içeren) ve donanım dizilerini veya HyperThreading'i (bir çekirdeğin iki gibi davranmasını sağlayan bir CPU özelliği) karıştırıyorsunuz.
ugoren

2
20 sürücüm ve 4 kamyonum var. Bir sürücünün iki kamyonla paketleri teslim etmesi nasıl mümkün olabilir? Bir kamyonun birden fazla sürücüye sahip olması nasıl mümkün olabilir? Her iki sorunun cevabı da aynı. Sırayla al.
Eric Lippert

Yanıtlar:


84

İşletim sistemi, çalışmaya uygun olan iş parçacıklarına zaman dilimi CPU s sunmaktadır .

Yalnızca bir çekirdek varsa, işletim sistemi bir çekirdek için o çekirdeği çalıştırmak için en uygun iş parçacığını zamanlar. Bir zaman dilimi tamamlandıktan sonra veya çalışan iş parçacığı IO'da engellediğinde veya işlemci harici olaylar tarafından kesildiğinde, işletim sistemi daha sonra hangi iş parçacığını çalıştıracağını yeniden değerlendirir (ve aynı iş parçacığını tekrar seçebilir veya farklı bir tane seçebilir).

Koşmaya uygunluğu adalet, öncelik ve hazır olma durumundaki değişikliklerden oluşur ve bu yöntemle çeşitli dişliler diğerlerinden daha fazla zaman dilimleri alır.

Birden fazla çekirdek varsa, N, işletim sistemi çekirdeklerde çalışacak en uygun N iş parçacığını zamanlar.

İşlemci benzeşimi bir verimlilik değerlendirmesidir. Bir CPU her öncekinden farklı bir iş parçacığı çalıştırdığında, biraz yavaşlama eğilimindedir, çünkü önbelleği önceki iş parçacığı için ılıktır, fakat yenisine soğuktur. Dolayısıyla, aynı iş parçacığının aynı işlemcide sayısız zaman diliminde çalıştırılması bir verimlilik avantajıdır.

Ancak, işletim sistemi farklı CPU'larda bir iş parçacığı zaman dilimi sunmakta serbesttir ve farklı zaman dilimlerinde tüm CPU'larda dönebilir. Bununla birlikte, @ gnasher729'un dediği gibi , aynı anda birden fazla CPU'da bir iş parçacığı çalıştıramaz.

Hyperthreading, donanımda, tek bir gelişmiş CPU çekirdeğinin aynı anda iki veya daha fazla farklı iş parçacığının yürütülmesini destekleyebileceği bir yöntemdir . (Böyle bir CPU silikon gayrimenkulünde ek tam çekirdeğe göre daha düşük maliyetle ek dişler sunabilir.) Bu gelişmiş CPU çekirdeği, CPU kayıt değerleri gibi diğer iş parçacıkları için ek durumu desteklemeli ve aynı zamanda koordinasyon durumuna ve davranışına sahiptir. dişlileri sıkıştırmadan bu CPU içindeki fonksiyonel birimlerin paylaşılmasını sağlar.

Hyperthreading, teknik olarak donanım açısından zorlanırken, programcının bakış açısından, yürütme modeli yalnızca daha karmaşık bir şey yerine ek CPU çekirdeğidir. Bu nedenle, işletim sistemi ek CPU çekirdeği görür, ancak bazı yeni işlemcili diziler bir CPU çekirdeğinin önbellek mimarisini paylaşırken bazı yeni işlemci benzeşimi sorunları vardır.


Doğal olarak, her biri kendi tam çekirdeğine sahip oldukları kadar hızlı çalıştığını düşünerek hiper lastikli bir göbek üzerinde çalışan iki dişin olduğunu düşünebiliriz. Ancak, bu mutlaka gerekli değildir, çünkü tek bir dişlinin yürütülmesi gevşek döngülerle doludur ve bunların bir kısmı diğer hiper iş parçacığı dişleri tarafından da kullanılabilir. Ayrıca, durgun olmayan döngülerde bile, bir diş diğerinden farklı fonksiyonel birimler kullanıyor olabilir, böylece eşzamanlı uygulama gerçekleşebilir. Hyperthreading için geliştirilmiş CPU, bunu desteklemek için özel olarak kullanılan bazı yoğun kullanılan fonksiyonel birimlere sahip olabilir.


3
“Böylece, aynı iş parçacığının aynı işlemcide sayısız zaman dilimi boyunca çalıştırılması bir verimlilik avantajıdır.” Bitişik zaman dilimleri olması gerekmez mi ? Aksi takdirde, önbellekler başka yivler tarafından silinir, değil mi? Güzel bir açıklama için +1.
jpmc26

2
@Luaan: HT genellikle iyidir, ancak durum tarif ettiğiniz kadar basit değildir. Ön uç sorunu bant genişliği (Intel'de saatte 4 uops, Ryzen'de 6), iş parçacıkları arasında eşit olarak paylaşılır (bir kişi durmadığı sürece). Eğer bu tıkanıklıksa, HT'nin yardım etmeyeceğini söylediğim gibi. Skylake'in iyi ayarlanmış bir döngüde buna yaklaşması nadir değildir, eğer yükler, ALU ve depolar varsa ... Transistörler ucuzdur (ve bir kerede anahtarlama yapamazlar ya da CPU erir), bu yüzden modern x86 işlemciler, ön uçların besleyebildiğinden daha fazla yürütme bağlantı noktasına sahipler (birçok yürütme birimi çoğaltılıyor ...
Peter Cordes

2
... birden fazla bağlantı noktasında) ... Bu bir atık gibi görünebilir, ancak çoğu zaman bir döngü bir kerede yalnızca bir tür ALU yürütme birimi kullanır, bu nedenle her şeyin yinelenmesine sahip olmak, ne tür bir kodun çalıştığını gösterir; talimatlar için portlar. Bu nedenle, HT’den yararlanmak için alıntı yaptığınız neden bu kadar yaygın değil, çünkü çoğu kodun ön uç bant genişliğini alan bazı yükleri ve / veya depoları var ve kalanlar genellikle yürütme birimlerini doyurmaya yetmiyor.
Peter Cordes

2
@Luaan: Ayrıca, Intel CPU'larda tamsayı ve FP / vektör yürütme birimleri aynı yürütme bağlantı noktalarını paylaşır . Örneğin, FP FMA / mul / add birimleri 0/1 portlarındadır. Ancak tamsayı çarpanı, aynı zamanda port1'de de bulunur ve basit tamsayı işlemleri, 4 yürütme portundan herhangi birinde çalışabilir (cevabımdaki diyagram). İkinci bir iş parçacığı sorun bant genişliği kullanarak yürütme birimleri için rekabet olmasa bile, her ikisini de yavaşlatır, ancak genellikle önbellek için çok kötü rekabet etmiyorlarsa net bir verimlilik artışı vardır. X264 / x265 (video kodlayıcılar) gibi iyi ayarlanmış yüksek verimli kodlar bile HT'den Skylake'de yaklaşık% 15 oranında fayda sağlar.
Peter Cordes

3
@luaan Peter'ın söylediğine ek olarak, "HT'nin arkasındaki özgün sebep buydu" iddiasının yanlış olduğu iddiası yanlış. HT arkasında orijinal muhakeme NetBurst mikromimari o dal mispredictions ve diğer boru hattı kabarcıklar kesinlikle (saat hız sürüş amacıyla) bu tür aşırı ölçüde boru hattını uzatılmıştır olduğuydu öldüren performansı. HT, bu büyük pahalı çipin yürütme ünitelerinin boru hattındaki kabarcıklar nedeniyle boşta durduğu süreyi en aza indiren çözümlerden biriydi: diğer dişlerden gelen kod bu deliklere yerleştirilip çalıştırılabilir.
Cody Gray

24

Aynı anda birden fazla çekirdek üzerinde çalışan tek bir iş parçacığı gibi bir şey yoktur.

Bununla birlikte, bir dişten gelen talimatların paralel olarak yürütülemeyeceği anlamına gelmez. Buna izin veren , talimat boru hatları ve sıra dışı çalıştırma denilen mekanizmalar var. Her bir çekirdeğin basit talimatlar tarafından kullanılmayan fazladan fazla kaynağı vardır, bu nedenle birden fazla bu tür komutlar birlikte çalıştırılabilir (birincisi önceki sonuca bağlı olmadığı sürece). Ancak, bu hala tek bir çekirdekte gerçekleşiyor.

Hiper iş parçacığı, bir çekirdeğin yalnızca bir iş parçacığından gelen komutları paralel olarak yürütmekle kalmayıp, kaynak kullanımını daha da iyileştirmek için iki iş parçacığından gelen yönergeleri karıştırdığı, bu fikrin çok çeşitli bir türevidir.

İlgili Vikipedi girdileri: Talimat boru hattı , sipariş dışı işlem .


3
Aynı anda kaçamazlar, ancak paralel olarak kaçabilirler mi? Bunlar aynı değil mi?
Evorlor

10
@Evorlor Buradaki anahtar şey, bir çekirdek ile bir yürütme birimi arasındaki farktır. Tek bir diş yalnızca bir çekirdekte çalışabilir, ancak bir işlemci çekirdek tarafından yürütülen talimatların birbirine bağlı olmadığını ve bunları aynı anda farklı yürütme birimlerinde aynı anda yürütebildiğini bulmak için dinamik analizi kullanabilir. Bir çekirdekte birkaç yürütme birimi olabilir.
user1937198

3
@Evorlor: Sıra dışı bir CPU, tek bir dişlinin komut akışı içindeki komut seviyesi paralelliği bulabilir ve kullanabilir . Örneğin, genellikle bir döngü sayacını güncelleyen talimatlar, bir döngünün yaptığı diğer çalışmalardan bazılarından bağımsızdır. Veya bir a[i] = b[i] + c[i]döngüde, her bir yineleme bağımsızdır, böylece farklı yinelemelerden yükler, ekler ve mağazalar aynı anda uçuşta olabilir. Program sırasına göre verilen talimatların, örneğin önbellekte eksik olan bir mağazanın ipliği geciktirmediği (mağaza tamponunda boşalana kadar) olduğu yanılsamasını korumalıdır.
Peter Cordes,

3
@ user1937198: "Dinamik analiz" ifadesi bir JIT derleyicisine daha uygun olacaktır. Sıra dışı CPU'lar gerçekten analiz etmiyor ; daha çok talimatların kodu çözülen ve verilen ve girişlerini hazırlayan her şeyi çalıştıran açgözlü bir algoritma gibidir. (Sıra dışı yeniden sıralama penceresi birkaç mikro mimari kaynakla sınırlıdır, örneğin Intel Sandybridge ReOrder Buffer 168 ups boyutundadır. Ayrıca ROB boyutunu deneysel olarak ölçmeye bakın ). Hepsi saat başına 4 uops işlemek için donanım devlet makineleri ile uygulanmaktadır.
Peter Cordes

3
@Luaan evet, ilginç bir fikirdi, ancak AOT derleyicileri hala tam olarak onu kullanabilecek kadar akıllı değil. Ayrıca, Linus Torvalds (ve diğerleri), boru hattının iç kısımlarının çoğunun gelecekteki tasarımlar üzerinde büyük bir kısıtlama olduğunu ortaya koymuştur. örneğin, ISA'yı değiştirmeden boru hattı genişliğini gerçekten artıramazsınız. Veya bağımlılıkları normal yoldan izleyen ve paralel olarak iki VLIW grubunu yayınlayan bir CPU oluşturursunuz, ancak daha sonra EPIC'in CPU karmaşıklığı avantajını kaybettiniz, ancak yine de dezavantajları var (derleyici dolduramazken sorun bant genişliği kaybettiniz) Bir kelime).
Peter Cordes

22

Özet: Tek-iş parçacıklı bir programdaki (komut seviyesi) paralelliği bulma ve kullanma, üzerinde çalıştığı CPU çekirdeği tarafından tamamen donanımda yapılır. Ve sadece birkaç yüz talimatın olduğu bir pencerede, büyük çapta yeniden sıralama değil.

Dışında Tek iş parçacıklı programlar, çok çekirdekli işlemcilerden hiçbir yararı olsun başka şeyler yerine tek iş parçacıklı görev uzak zaman ayırdığınız diğer çekirdek çalışabilir.


İşletim sistemi tüm iş parçacıklarının talimatlarını birbirlerini beklemeyecek şekilde düzenler.

İşletim sistemi, ipliklerin talimat akışlarına bakmaz. Sadece çekirdeği çekirdeğe zamanlar.

Aslında, her bir çekirdek, daha sonra ne yapacağını bulması gerektiğinde işletim sisteminin zamanlayıcı işlevini çalıştırır. Çizelgeleme dağıtılmış bir algoritmadır. Çok çekirdekli makineleri daha iyi anlamak için her çekirdeğin çekirdeği ayrı çalıştığını düşünün. Tıpkı çok iş parçacıklı bir program gibi, çekirdek de paylaşılır veri yapılarını (çalışmaya hazır olan iş parçacıklarının listesi gibi) güncellemek için bir çekirdekteki kodunun diğer çekirdeklerdeki kodlarıyla güvenli bir şekilde etkileşime girebilmesi için yazılmıştır.

Yine de, işletim sistemi çok iş parçacıklı işlemlerin , çok iş parçacıklı bir programın elle yazılmasıyla açıkça ortaya konması gereken iş parçacığı düzeyinde paralellikten yararlanmasına yardımcı olmakla ilgilenmektedir . (Veya OpenMP veya benzeri bir şey ile otomatik paralel bir derleyici tarafından ).

Ardından CPU'nun ön ucu ayrıca her bir çekirdeğe bir iş parçacığı dağıtarak bu talimatları düzenler ve her iş parçasından bağımsız komutları açık döngüler arasında dağıtır.

Bir CPU çekirdeği, durdurulmadığında yalnızca bir talimat akışını çalıştırıyordur (bir sonraki kesintiye kadar uyuyor, örneğin zamanlayıcı kesintisi). Genellikle bu bir iş parçacığıdır, ancak aynı zamanda bir iş parçacığı kesme işleyicisi veya başka bir çekirdek kodu, eğer çekirdeğin taşıma ve kesme ya da sistem çağrısından sonra bir önceki iş parçasına dönmekten başka bir şey yapmaya karar vermesi durumunda olabilir.

HyperThreading veya diğer SMT tasarımlarında, fiziksel bir CPU çekirdeği çoklu "mantıksal" çekirdekler gibi davranır. Dört çekirdekli, yüksek okuyuculu dört çekirdekli (4c8t) bir CPU ve düz 8 çekirdekli bir makine (8c8t) arasındaki bir işletim sistemi perspektifinden tek fark, HT tanıyan bir işletim sisteminin fiziksel çekirdeği ayırmak için konuları programlamaya çalışacak olmalarıdır. t birbirleriyle yarışmak. Hiper-dişlemeyi bilmeyen bir işletim sistemi sadece 8 çekirdeği görecektir (BIOS'ta HT'yi devre dışı bırakmazsanız, sadece 4'ü algılar).


" Ön uç" terimi, makine kodunu alan, talimatların kodunu çözen ve bunları çekirdeğin sıra dışı parçasına veren bir CPU çekirdeğinin bir parçasını belirtir . Her çekirdeğin kendi ön ucu var ve bir bütün olarak çekirdeğin bir parçası. O getirir Talimatlar olan CPU anda çalışan ne.

Çekirdeğin sıra dışı bölümünde, giriş işleyicileri hazır olduğunda ve boş bir yürütme bağlantı noktası bulunduğunda komutlar (veya uops) yürütme bağlantı noktalarına gönderilir. Bu, program sırasına göre gerçekleşmek zorunda değildir, bu nedenle bir OOO işlemcisinin tek bir iş parçacığı içindeki komut düzeyinde paralellikten nasıl yararlanabileceğidir .

"Çekirdek" i fikrinizde "yürütme birimi" ile değiştirirseniz, düzeltmeye yakınsınız. Evet, CPU, yürütme birimlerine paralel olarak bağımsız talimatlar / uops dağıtır. (Ancak bir terminoloji karışımı var, çünkü gerçekten CPU'nun talimat zamanlayıcısı olan Reservation Station çalıştırılmaya hazır olanları seçtiğinde "front-end" dediğinizden beri).

Sıra dışı yürütme, ILP'yi yalnızca yerel düzeyde, iki bağımsız döngü arasında (kısa olmadıkça) birkaç yüze kadar talimat bulabilir.


Örneğin, bunun asm eşdeğeri

int i=0,j=0;
do {
    i++;
    j++;
} while(42);

Intel Haswell'de yalnızca bir sayacı artıran aynı döngü kadar hızlı çalışacaktır. i++Sadece önceki değerine bağlıdır iiken, j++sadece önceki değerine bağlıdır j, böylece iki bağımlılık zincirleri programı sırayla yürütülmektedir her şeyin yanılsama bozmadan paralel olarak çalışabilir.

X86'da, döngü şöyle görünür:

top_of_loop:
    inc eax
    inc edx
    jmp .loop

Haswell'de 4 tamsayı yürütme bağlantı noktası vardır ve hepsinde ek birim vardır, bu nedenle inchepsi bağımsızsa, saat başına 4 adede kadar komut verimini sürdürebilir . (Gecikme = 1 olduğunda, yalnızca 4 inctalimatı uçuşta tutarak verimi en üst düzeye çıkarmak için yalnızca 4 yazıcınıza ihtiyacınız olur. Kontrastı, vektör-FP MUL veya FMA ile kontrastlandırın: gecikme = 5 çıkış = 0.5, 10 FMA'yı uçuşta tutmak için 10 vektör akümülatöre ihtiyaç duyar Verimi en üst düzeye çıkarmak için Ve her bir vektör 256b olabilir, 8 tek duyarlıklı yüzer tutan).

Alınan dal aynı zamanda bir tıkanıklıktır: ilmek her zaman yineleme başına en az bir tam saat alır, çünkü alınan dal verimi her saat başı 1 ile sınırlıdır. Performansı düşürmeden döngü içinde bir talimat daha ekleyebilirim, ayrıca okuma / yazma eaxveya edxbu durumda bağımlılık zincirini uzatacaksa. Döngü içine (veya bir karmaşık multi-uop komutundan) 2 komut daha koymak, ön uçta bir tıkanıklık yaratacaktır; ( 4 uop'un katı olmayan döngüler için neler olduğuna ilişkin bazı ayrıntılar için bu SO Q&A'ya bakın : loop-buffer ve uop önbelleği işleri ilginç hale getirir.)


Daha karmaşık durumlarda, paralelliği bulmak daha geniş bir talimat penceresine bakmayı gerektirir . (örneğin, belki hepsinin birbirine bağlı, sonra bazı bağımsız olanlar için 10 komut dizisi vardır).

Yeniden Sipariş Arabellek kapasitesi, sıra dışı pencere boyutunu sınırlayan faktörlerden biridir. Intel Haswell'de 192 ay. (Ayrıca , kayıt defteri yeniden adlandırma kapasitesi (kayıt dosyası boyutu) ile birlikte deneysel olarak da ölçebilirsiniz .) ARM gibi düşük güçlü CPU çekirdeği, sıra dışı çalıştırma yaparlarsa çok daha küçük ROB boyutlarına sahiptir.

Ayrıca, CPU'ların sıra dışı olmasının yanı sıra boru hattında olması gerektiğini de unutmayın. Bu nedenle, çalıştırılmakta olanlardan çok daha önce, tercihen herhangi bir alım döngüsünü kaybettikten sonra tamponları tekrar doldurmak için yeterli verim ile talimatları almak ve kodunu çözmek zorundadır. Şubeler zor, çünkü bir şubenin hangi yöne gittiğini bilmiyorsak nereden getireceğimizi bile bilmiyoruz. Bu nedenle şube kestirimi çok önemlidir. (Ve neden modern CPU'lar spekülatif yürütme kullanıyorlar: bir dalın hangi yöne gideceğini ve bu komut akışını almaya / deşifre etmeye / yürütmeye başlayacağını tahmin ediyorlar. Bir yanlışlık tespit edildiğinde, bilinen en son iyi duruma geri dönüp oradan yürütüyorlar.)

İşlemci dahili değerleri hakkında daha fazla bilgi edinmek istiyorsanız, Stackoverflow x86 etiketi wiki'de , Agner Fog'un microarch kılavuzuna ve David Kanter'in Intel ve AMD CPU diyagramları içeren ayrıntılı yazılarına dahil olmak üzere bazı bağlantılar vardır . Intel Haswell mikro mimari yazısından , bu bir Haswell çekirdeğinin (çipin tamamı değil) tüm boru hattının son diyagramıdır.

Bu, tek bir CPU çekirdeğinin blok şemasıdır . Dört çekirdekli bir işlemci, bunlardan 4'ünü bir çipte, her biri kendi L1 / L2 önbelleklerine sahip (bir L3 önbellek, bellek denetleyicileri ve sistem cihazlarına PCIe bağlantılarını paylaşıyor) sahip.

Haswell dolu boru hattı

Bunun çok karmaşık olduğunu biliyorum. Kanter'in makalesi, örneğin ön kısım hakkında yürütme birimlerinden veya önbelleklerden ayrı olarak konuşulacak kısımlarını da göstermektedir.


2
"Tek iş parçacıklı bir programda (komut düzeyinde) paralelliği bulma ve kullanma yalnızca donanımda yapılır" Bu, yalnızca ILP'nin derleyici veya programcı tarafından tam olarak belirlendiği VLIW'lar için değil, donanım arasında işbirliği içinde olduğu geleneksel ISA'lar için geçerlidir. ve yazılım.
Hadi Brais

1
@ user7813604: evet. Hyperthreading tek bir dişe paralel olamaz. Tersini yapar: bir çekirdekte birden fazla diş çalıştırır, diş başına performansı düşürür fakat toplam verimi arttırır.
Peter Cordes,

1
@ user7813604: ILP'nin bütün noktası, her bir talimatın sırayla çalıştığı yanılsamasını korurken , her biri bir sonrakinden önce bitirme olan, hangi komutların paralel olarak çalıştırılabileceğini bulmaktır . Bir skaler boru hattlı CPU, gecikme süresi 1'den yüksekse, bazen bağımlılıklar için durmaya ihtiyaç duyabilir. Fakat süperskalar işlemciler için daha da büyük bir anlaşma.
Peter Cordes,

1
@ user7813604: evet, cevabım tam anlamıyla bunu örnek olarak kullanıyor. Örneğin Haswell inc, aynı saat çevriminde 4 adede kadar komutu, 4 tamsayılı ALU yürütme birimine uygulayabilir.
Peter Cordes,

1
@ user7813604: Evet İLP ne kadar olabilir paralel olarak yürütülecek. Gerçek bir CPU, ILP'yi tek bir çekirdekte paralel olarak çalıştırarak, örneğin Intel'de 4 genişliğe kadar süperskalar'a kadar çalıştırarak ILP'yi bulma ve kullanma becerisine sahip olacaktır. Bu cevap bunu örneklerle açıklamaya çalışır.
Peter Cordes,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.