Kalıcı veri yapılarının işlevsel olmayan dillerde kullanımı


17

Tamamen işlevsel veya neredeyse tamamen işlevsel olan diller, değişmez oldukları ve vatansız fonksiyonel programlama tarzına iyi uydukları için kalıcı veri yapılarından yararlanırlar.

Ancak zaman zaman Java gibi (devlet tabanlı, OOP) diller için kalıcı veri yapıları kütüphaneleri görüyoruz. Kalıcı veri yapıları lehine sıkça duyulan bir iddia, değişmez oldukları için iplik güvenli olduklarıdır .

Ancak, kalıcı veri yapılarının iş parçacığı açısından güvenli olmasının nedeni, bir iş parçacığının kalıcı bir koleksiyona "öğe" ekleyebilmesi durumunda, işlemin orijinal gibi ancak öğe eklenmiş olarak yeni bir koleksiyon döndürmesidir . Bu nedenle diğer iplikler orijinal koleksiyona bakınız. İki koleksiyon elbette çok fazla iç durumu paylaşıyor - bu yüzden bu kalıcı yapılar etkili.

Farklı ipler verilerin farklı durumlarını görmek beri Ancak, kalıcı veri yapıları olduğunu görünüyor değil bir iş parçacığı diğer iş parçacığı tarafından görülebilir bir değişiklik yapar; kendilerinin de senaryolar işlemek için yeterli. Bunun için atomlar, referanslar, yazılım işlem belleği, hatta klasik kilitler ve senkronizasyon mekanizmaları gibi cihazları kullanmamız gerektiği görülmektedir.

Öyleyse neden PDS'lerin değişmezliği "iplik güvenliği" için faydalı bir şey olarak lanse ediliyor? PDS'lerin eşzamanlama veya eşzamanlılık sorunlarını çözmede yardımcı olduğu gerçek örnekler var mı? Yoksa PDS'ler işlevsel bir programlama stilini destekleyen bir nesneye durum bilgisi olmayan bir arayüz sağlamanın bir yolu mudur?


3
"Kalıcı" diyorsun. Gerçekten de "programın yeniden başlamasında hayatta kalabilmek için" olduğu gibi "kalıcı" mı, yoksa "oluşturulduktan sonra asla değişmediğinde" olduğu gibi "değişmez" mi demek istediniz?
Kilian Foth

17
@KilianFoth Kalıcı veri yapıları iyi tanımlanmış bir tanıma sahiptir : "kalıcı bir veri yapısı, değiştirildiğinde her zaman önceki sürümünü koruyan bir veri yapısıdır". Bu nedenle, "bir programın yeniden başlatılmasında hayatta kalabilmek" gibi kalıcılık yerine yeni bir yapı oluşturulduğunda önceki yapıyı yeniden kullanmakla ilgilidir.
Michał Kosmulski

3
Sorunuz, işlevsel olmayan dillerde kalıcı veri yapılarının kullanımı ve paradigmadan bağımsız olarak hangi eşzamanlılık ve paralellik bölümlerinin onlar tarafından çözülmediği hakkında daha az gibi görünüyor .

Benim hatam. "Kalıcı veri yapısı" nın sadece kalıcılıktan farklı bir teknik terim olduğunu bilmiyordum.
Kilian Foth

@delnan Evet, doğru.
Ray Toal

Yanıtlar:


15

Kalıcı / değişmez veri yapıları eşzamanlılık problemlerini kendi başlarına çözmez, ancak çözmeyi çok daha kolay hale getirir.

Bir seti S başka bir T2 dişine geçiren bir iplik T1'i düşünün. S değişebiliyorsa, T1'in bir sorunu vardır: S ile olanların kontrolünü kaybeder. T2 dişi onu değiştirebilir, bu nedenle T1 S içeriğine güvenemez ve tersi de - T2 T1'in T1 T2 üzerinde çalışırken S'yi değiştirmez.

Bir çözüm, T1 ve T2'nin iletişimine bir tür sözleşme eklemektir, böylece ipliklerden sadece birinin S'yi değiştirmesine izin verilir. Bu hataya eğilimlidir ve hem tasarım hem de uygulama için yük oluşturur.

Başka bir çözüm, T1 veya T2'nin veri yapısını (veya koordine edilmezlerse her ikisini) klonlamasıdır. Ancak, S kalıcı değilse, bu pahalı bir O (n) işlemidir.

Kalıcı bir veri yapınız varsa, bu yükten kurtulursunuz. Bir yapıyı başka bir iş parçacığına geçirebilirsiniz ve onunla ne yaptığına dikkat etmeniz gerekmez. Her iki iş parçacığı da orijinal sürüme erişebilir ve üzerinde rasgele işlemler yapabilir - diğer iş parçacığının gördüklerini etkilemez.

Ayrıca bakınız: kalıcı ve değişmez veri yapısı .


2
Yani, bu bağlamda "iş parçacığı güvenliği" sadece bir iş parçacığının gördükleri verileri yok eden diğer iş parçacıkları hakkında endişelenmesi gerekmediği, ancak iş parçacıkları arasında paylaşılmasını istediğimiz verilerle senkronizasyon ve ilgilenmeyle ilgisi olmadığı anlamına gelir . Düşündüğüm şeyle aynı, ancak +1 şık bir şekilde "para birimi sorunlarını kendi başlarına çözmeyin."
Ray Toal

2
@RayToal Evet, bu bağlamda "iş parçacığı güvenli" tam olarak bu demektir. Verilerin iş parçacıkları arasında nasıl paylaşıldığı, belirttiğiniz gibi birçok çözümü olan farklı bir sorundur (kişisel olarak bileşimi için STM'yi seviyorum). İş parçacığı güvenliği, paylaşıldıktan sonra verilerde neler olduğu konusunda endişelenmenize gerek kalmamasını sağlar. Bu aslında büyük bir iştir, çünkü iş parçacıklarının bir veri yapısı üzerinde kimin ne zaman çalıştığını senkronize etmesi gerekmez.
Petr Pudlák

@RayToal Bu, geliştiricilerin açık kilitleme ve iş parçacığı yönetimi ile uğraşmak zorunda kalmayan ve mesajların değişmezliğine dayanan aktörler gibi zarif eşzamanlılık modellerine izin verir - bir mesajın ne zaman teslim edildiğini ve işlendiğini bilmiyorsunuz yönlendirildiği aktörler.
Petr Pudlák

Teşekkürler Petr, oyunculara bir kez daha bakacağım. Tüm Clojure mekanizmalarına aşinayım ve Rich Hickey'in açıkça , en azından Erlang'da örneklendiği gibi aktör modelini kullanmamayı seçtiğini not ettim . Yine de ne kadar çok bilirsen o kadar iyi.
Ray Toal

@RayToal İlginç bir bağlantı, teşekkürler. Oyuncuları sadece örnek olarak kullandım, bunun en iyi çözüm olacağını söylemiyorum. Clojure kullanmadım, ancak tercih edilen çözümün kesinlikle oyunculardan çok tercih edeceğim STM olduğu anlaşılıyor. STM ayrıca kalıcılığa / değişmezliğe de bağlıdır - bir veri yapısını değiştirilemez şekilde değiştirirse bir işlemi yeniden başlatmak mümkün olmaz.
Petr Pudlák

5

Öyleyse neden PDS'lerin değişmezliği "iplik güvenliği" için faydalı bir şey olarak lanse ediliyor? PDS'lerin eşzamanlama veya eşzamanlılık sorunlarını çözmede yardımcı olduğu gerçek örnekler var mı?

Bu durumda bir PDS'nin ana yararı, verilerin bir kısmını her şeyi benzersiz hale getirmeden (her şeyi derin kopyalamaksızın, tabiri caizse) değiştirebilmenizdir. Bunun yan etkileri olmayan ucuz işlevler yazmanıza izin vermenin yanı sıra birçok potansiyel faydası vardır: kopyalama ve yapıştırılan verileri yerleştirme, önemsiz geri alma sistemleri, oyunlarda önemsiz yeniden oynatma özellikleri, önemsiz tahribatsız düzenleme, önemsiz istisna güvenliği vb.


2

Kalıcı fakat değişebilen bir veri yapısı düşünülebilir. Örneğin, ilk düğüme bir işaretçi ile temsil edilen bağlantılı bir liste ve yeni bir başlık düğümü artı bir önceki listeden oluşan yeni bir liste döndüren bir ön-işlem alabilirsiniz. Hala önceki başlığa referansınız olduğundan, bu listeye yeni listenin içine gömülmüş olan bu listeye erişebilir ve bunları değiştirebilirsiniz. Mümkün olsa da, böyle bir paradigma, kalıcı ve değişmez veri yapılarının faydalarını sunmaz, örneğin, varsayılan olarak kesinlikle güvenli değildir. Ancak, geliştirici ne yaptığını bildiği sürece, örneğin alan verimliliği için kullanımlarına sahip olabilir. Ayrıca, yapının dil düzeyinde değiştirilebilmesine rağmen, kodun hiçbir şeyi değiştirmesini engellememesi nedeniyle,

Bu kadar uzun hikaye kısa, değişmezlik olmadan (dil veya konvansiyon tarafından zorlanan), veri yapılarının kalıcılığı bazı faydalarını kaybeder (iş parçacığı güvenliği), ancak diğerleri (bazı senaryolar için alan verimliliği).

İşlevsel olmayan dillerden örneklere gelince, Java'lar String.substring()kalıcı veri yapısı dediğim şeyi kullanır. Dize, bir karakter dizisinin yanı sıra gerçekten kullanılan dizinin başlangıç ​​ve bitiş ofsetleriyle temsil edilir. Bir alt dize oluşturulduğunda, yeni nesne aynı karakter dizisini yalnızca değiştirilmiş başlangıç ​​ve bitiş ofsetleriyle yeniden kullanır. Yana Stringdeğişmez, bu (göre olan substring()değişmez bir kalıcı veri yapısı işlemi değil, diğerleri).

Veri yapılarının değişmezliği, iplik güvenliği ile ilgili kısımdır. Kalıcılıkları (yeni bir yapı oluşturulduğunda mevcut parçaların yeniden kullanımı), bu tür koleksiyonlarla çalışırken verimlilikle ilgilidir. Değişmez olduklarından, bir öğe eklemek gibi bir işlem mevcut yapıyı değiştirmez ancak ek öğe eklenmiş olarak yeni bir yapı döndürür. Tüm yapı her kopyalandığında, boş bir koleksiyonla başlayıp 1000 elementlik bir koleksiyonla sonuçlanmak için 1000 öğeyi tek tek ekleyerek, 0 + 1 + 2 + ... + 999 = Toplam 500000 element büyük bir atık olur. Kalıcı veri yapılarında, 1 elemanlı toplama 2 elemanlı birde tekrar kullanıldığından, 3 elemanlı birde tekrar kullanılan vb.


Bazen, devletin bir yönü dışında hepsinin değişmez olduğu yarı değişmez nesnelere sahip olmak yararlı olabilir: durumu neredeyse belirli bir nesne gibi olan bir nesneyi yapma yeteneği. Örneğin, AppendOnlyList<T>büyüyen iki diziden oluşan bir destek, her bir anlık görüntü için herhangi bir veri kopyalamak zorunda kalmadan değiştirilemeyen anlık görüntüler üretebilir, ancak bu tür bir anlık görüntünün içeriğini ve yeni bir öğeyi yeniden kopyalamadan içeren bir liste üretilemedi her şeyi yeni bir diziye.
supercat

0

Kuşkusuz bu tür kavramları dil ve doğası, etki alanı ve hatta dili kullanma şeklimize göre C ++ 'da uygulayan bir önyargılıyım. Ama bunlar göz önüne alındığında, bence değişmez tasarımlar o iplik güvenliği, sistem hakkında akıl kolaylığı, fonksiyonlar için daha fazla yeniden kullanımını bulma (ve biz olabilir bulma gibi, fonksiyonel programlama ile ilişkili yararlar toplu hasat geldiğinde en az ilginç yönü vardır hoş olmayan sürprizler olmadan bunları herhangi bir sırada birleştirin), vb.

Bu basit C ++ örneğini alın (kuşkusuz, herhangi bir görüntü işleme uzmanının önünde kendimi utandırmaktan kaçınmak için basitlik için optimize edilmemiştir):

// Inputs an image and outputs a new one with the specified size.
Image resized_image(const Image& src, int new_w, int new_h)
{
     Image dst(new_w, new_h);
     for (int y=0; y < new_h; ++y)
     {
         for (int x=0; x < new_w; ++x)
              dst[y][x] = src.sample(x / (float)new_w, y / (float)new_h);
     }
     return dst;
}

Bu işlevin uygulanması, yerel (ve geçici) durumu iki sayaç değişkeni ve çıktı için geçici bir yerel görüntü biçiminde değiştirirken, dış yan etkileri yoktur. Bir görüntü girer ve yeni bir görüntü verir. Bunu kalplerimizin içeriğine çoğaltabiliriz. Akıl yürütmek kolay, iyice test etmek kolaydır. İstisnai olarak güvenlidir, çünkü bir şey atılırsa, yeni görüntü otomatik olarak atılır ve harici yan etkileri geri alma konusunda endişelenmemize gerek kalmaz (tabiri caizse, işlevin kapsamı dışında değiştirilen harici görüntüler yoktur).

ImageYukarıdaki fonksiyonun uygulanmasını daha uygunsuz ve muhtemelen biraz daha az verimli hale getirmesi dışında, yukarıdaki bağlamda, C ++ 'da değişmez hale getirilerek kazanılacak çok az ve potansiyel olarak kaybedilecek çok şey görüyorum .

Saflık

Bu yüzden saf işlevler ( dış yan etkilerden arınmış ) benim için çok ilginç ve C ++ 'da bile ekip üyelerine sık sık tercih etmenin önemini vurguluyorum. Ancak, genellikle bağlam ve nüans olmadan uygulanan değişmez tasarımlar benim için neredeyse ilginç değil, çünkü dilin zorunlu doğası göz önüne alındığında, bazı yerel geçici nesneleri verimli bir şekilde mutasyona uğratmak genellikle yararlı ve pratiktir (her ikisi de geliştirici ve donanım için).

Ağır Yapıların Ucuz Kopyalanması

Bulduğum ikinci en kullanışlı özellik, sık sık giriş / çıkış doğası göz önüne alındığında, işlevleri saf hale getirmek için sık sık olacağı gibi, gerçekten ağır veri yapılarını ucuza kopyalama yeteneğidir. Bunlar yığına sığabilecek küçük yapılar olmazdı. SceneBir video oyunu için olduğu gibi büyük, ağır yapılar olurdu .

Bu durumda kopyalama yükü, etkili paralellik fırsatlarını önleyebilir, çünkü fizik, oluşturucunun eşzamanlı olarak çizmeye çalıştığı sahneyi eşzamanlı olarak fizik yaparken mutasyona uğratıyorsa, fiziği birbirine kilitlemeden ve darboğaz yapmadan etkili bir şekilde paralel hale getirmek ve verimli hale getirmek zor olabilir. tüm oyun sahnesini kopyalamak, fizik uygulanan bir kareyi çıktılamak için eşit derecede etkisiz olabilir. Ancak, fizik sistemi sadece bir sahne girmesi ve fizik uygulanan yeni bir sahne çıkarması anlamında 'saf' olsaydı ve bu saflık astronomik kopyalama yükü pahasına gelmediyse, güvenli bir şekilde paralel olarak çalışabilirdi. biri diğerini beklemeden render.

Böylece, uygulama durumunuzun gerçekten ağır verilerini ucuza kopyalama ve işleme ve bellek kullanımı için minimum maliyetle yeni, değiştirilmiş sürümler çıktılama yeteneği, saflık ve etkili paralellik için yeni kapılar açabilir ve orada öğrenecek çok ders buluyorum kalıcı veri yapılarının nasıl uygulandığından Ancak bu tür dersleri kullanarak yarattığımız her şeyin tamamen kalıcı olması veya değişmez arayüzler sunması gerekmez (bu, ucuza dönüşme yeteneğini elde etmek için, üzerine yazılırken kopyala veya bir "kurucu / geçici" kullanabilir). işlevlerimiz / sistemlerimiz / boru hattımızdaki paralellik ve saflık arayışımızda bellek kullanımını ve bellek erişimini ikiye katlamadan kopyalamanın yalnızca bölümlerini değiştirmek ve değiştirmek.

değişmezlik

Son olarak, bu üçünden en az ilginç olduğunu düşündüğüm değişmezlik var, ancak bazı nesne tasarımlarının saf bir işleve yerel geçişler olarak kullanılması amaçlanmadığında ve daha geniş bir bağlamda, değerli bir demir yumrukla zorlayabilir. tüm yöntemlerde olduğu gibi artık harici yan etkilere neden olmaz (artık yöntemin hemen yerel kapsamı dışında üye değişkenleri değiştirmez).

C ++ gibi dillerde bu üçünün en az ilginç olduğunu düşünürken, kesinlikle önemsiz olmayan nesnelerin test edilmesini ve iş parçacığı güvenliğini ve mantığını basitleştirebilir. Örneğin, bir nesneye yapıcısının dışında herhangi bir benzersiz durum kombinasyonu verilemeyeceği ve sabitlik ve okumaya dayanmadan referans / işaretçi ile bile özgürce aktarabileceğimiz garantisiyle çalışmak bir yük olabilir. sadece yineleyiciler ve kulplar ve benzeri, orijinal içeriğinin mutasyona uğramayacağını garanti ederken (en azından dil içinde olabildiğince iyi).

Ama bunu en az ilginç olan özellik olarak görüyorum çünkü çoğu nesneyi geçici olarak, değiştirilebilir biçimde, saf bir işlevi (veya bir nesne veya dizi olabilecek bir "saf sistem" gibi daha geniş bir konsepti uygulamak için başka bir şeye dokunmadan sadece bir şey girmenin ve yeni bir şey çıkarmanın nihai etkisi ile işlev görür) ve bence aşırı derecede zorunlu bir dilde ekstremitelere taşınan değişmezlik oldukça karşı-üretken bir hedef. Kod tabanının gerçekten en çok yardımcı olduğu kısımlar için idareli bir şekilde uygularım.

En sonunda:

[...] kalıcı veri yapılarının kendi içlerinde bir iş parçacığının diğer iş parçacıkları tarafından görülebilir bir değişiklik yaptığı senaryoları işlemek için yeterli olmadığı görülüyor. Bunun için atomlar, referanslar, yazılım işlem belleği, hatta klasik kilitler ve senkronizasyon mekanizmaları gibi cihazları kullanmamız gerektiği görülmektedir.

Doğal olarak, tasarımınız değişikliklerin (kullanıcı sonu tasarım anlamında) meydana geldiğinde aynı anda birden çok iş parçacığına görünmesini gerektiriyorsa, senkronizasyona veya en azından çizim tahtasına bununla başa çıkmanın bazı karmaşık yollarını bulmak için geri dönüyoruz ( Fonksiyonel programlamada bu tür problemlerle uğraşan uzmanlar tarafından kullanılan çok ayrıntılı örnekler gördüm).

Ama buldum, bu tür bir kopyalama ve iri yapıların kısmen değiştirilmiş versiyonlarını çıkarma kabiliyetini elde ettikten sonra, örnek olarak kalıcı veri yapıları ile alacağınız gibi, genellikle çok sayıda kapı ve fırsat açıyor daha önce katı bir G / Ç tür paralel boru hattında birbirinden tamamen bağımsız olarak çalışabilen kodu paralelleştirmeyi düşünmemiştim. Algoritmanın bazı bölümleri doğada seri olmak zorunda olsa bile, bu işlemi tek bir iş parçacığına erteleyebilir, ancak bu kavramlara yaslanmanın, endişe duymadan, ağır işlerin% 90'ını paralelleştirdiğini görebilirsiniz.

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.