Özellikle Google'ın büyük ölçüde paralel hesaplama sistemi bağlamında, eşleme / azaltma hakkında çok şey duyuyorum. Tam olarak nedir?
Özellikle Google'ın büyük ölçüde paralel hesaplama sistemi bağlamında, eşleme / azaltma hakkında çok şey duyuyorum. Tam olarak nedir?
Yanıtlar:
Google'ın MapReduce araştırma yayın sayfasının özetinden :
MapReduce bir programlama modeli ve büyük veri kümelerini işlemek ve oluşturmak için ilişkili bir uygulamadır. Kullanıcılar, bir dizi ara anahtar / değer çifti oluşturmak için bir anahtar / değer çiftini işleyen bir eşleme işlevini ve aynı ara anahtarla ilişkili tüm ara değerleri birleştiren bir azaltma işlevini belirtir.
MapReduce'un avantajı, işlemenin birden çok işlem düğümünde (birden çok sunucu) paralel olarak gerçekleştirilebilmesi ve dolayısıyla çok iyi ölçeklenebilen bir sistem olmasıdır.
Fonksiyonel programlama modeline dayandığından , map
ve reduce
adımlarının her birinin herhangi bir yan etkisi yoktur (bir map
sürecin her bir alt bölümünün durumu ve sonuçları diğerine bağlı değildir), bu nedenle eşleştirilen ve indirgenen veri kümelerinin her biri ayrılabilir. çoklu işlem düğümleri üzerinden.
Joel's Programlama Diliniz Bunu Yapabilir mi? parça, Google'da arama motoruna güç veren MapReduce'u bulmak için işlevsel programlamayı anlamanın ne kadar önemli olduğunu tartışıyor. İşlevsel programlamaya ve ölçeklenebilir koda nasıl izin verdiğine aşina değilseniz, bu çok iyi bir okuma.
Ayrıca bakınız: Wikipedia: MapReduce
İlgili soru: Lütfen mapreduce'u basitçe açıklayın
Yapabileceğimden daha iyi açıklıyor. Yardımcı olur mu?
Harita, bir listedeki tüm öğelere başka bir işlevi uygulayan, tüm dönüş değerleri ile başka bir liste oluşturan bir işlevdir. ("F'yi x'e uygula" demenin başka bir yolu da "f'yi çağır, x'i geç" tir. Bu nedenle bazen "ara" yerine "uygula" demek daha hoş gelebilir.)
Harita muhtemelen C # ile nasıl yazılır (denir Select
ve standart kitaplıkta bulunur):
public static IEnumerable<R> Select<T, R>(this IEnumerable<T> list, Func<T, R> func)
{
foreach (T item in list)
yield return func(item);
}
Sen bir Java adamı olduğun için ve Joel Spolsky BÜYÜK YALANLARA Java'nın ne kadar berbat olduğunu söylemeyi seviyor (aslında yalan söylemiyor, berbat, ama seni kazanmaya çalışıyorum), işte benim çok kaba girişimim bir Java sürümü (Java derleyicim yok ve Java sürüm 1.1'i belli belirsiz hatırlıyorum!):
// represents a function that takes one arg and returns a result
public interface IFunctor
{
object invoke(object arg);
}
public static object[] map(object[] list, IFunctor func)
{
object[] returnValues = new object[list.length];
for (int n = 0; n < list.length; n++)
returnValues[n] = func.invoke(list[n]);
return returnValues;
}
Eminim bu, milyonlarca şekilde geliştirilebilir. Ama temel fikir bu.
Azaltma, bir listedeki tüm öğeleri tek bir değere dönüştüren bir işlevdir. Bunu yapmak için, func
iki öğeyi tek bir değere dönüştüren başka bir işlevin verilmesi gerekir . İlk iki maddeyi vererek işe yarayacaktır func
. Sonra üçüncü maddeyle birlikte bunun sonucu. Sonra bunun sonucu dördüncü maddeyle birlikte ve böylece tüm öğeler gidene ve tek bir değerle kalana kadar devam edecek.
C # 'da küçültme çağrılır Aggregate
ve yine standart kitaplıkta bulunur. Doğrudan bir Java sürümüne geçeceğim:
// represents a function that takes two args and returns a result
public interface IBinaryFunctor
{
object invoke(object arg1, object arg2);
}
public static object reduce(object[] list, IBinaryFunctor func)
{
if (list.length == 0)
return null; // or throw something?
if (list.length == 1)
return list[0]; // just return the only item
object returnValue = func.invoke(list[0], list[1]);
for (int n = 1; n < list.length; n++)
returnValue = func.invoke(returnValue, list[n]);
return returnValue;
}
Bu Java sürümlerinin eklenmesi gereken jenerikler, ancak bunu Java'da nasıl yapacağımı bilmiyorum. Ancak, functors sağlamak için onlara anonim iç sınıfları geçirebilmelisiniz:
string[] names = getLotsOfNames();
string commaSeparatedNames = (string)reduce(names,
new IBinaryFunctor {
public object invoke(object arg1, object arg2)
{ return ((string)arg1) + ", " + ((string)arg2); }
}
Umarım jenerikler dökümlerden kurtulur. C # 'daki tip güvenli eşdeğeri:
string commaSeparatedNames = names.Aggregate((a, b) => a + ", " + b);
Bu neden "harika"? Büyük hesaplamaları daha küçük parçalara ayırmanın basit yolları, böylece farklı şekillerde bir araya getirilebilir, her zaman harikadır. Google'ın bu fikri uygulama şekli paralelleştirmedir çünkü hem harita hem de küçültme birkaç bilgisayar üzerinden paylaşılabilir.
Ancak temel gereksinim, dilinizin işlevleri değerler olarak ele alabilmesi DEĞİLDİR. Herhangi bir OO dili bunu yapabilir. Paralelleştirme için gerçek gereksinim, eşlemeye func
geçirdiğiniz ve azalttığınız küçük işlevlerin herhangi bir durumu kullanmaması veya güncellememesidir. Yalnızca kendilerine iletilen argümanlara bağlı bir değer döndürmeleri gerekir. Aksi takdirde, her şeyi paralel olarak çalıştırmaya çalıştığınızda sonuçlar tamamen alt üst olur.
Çok uzun waffley veya çok kısa belirsiz blog gönderileriyle en çok hayal kırıklığına uğradıktan sonra, sonunda bu çok iyi titiz özlü makaleyi keşfettim. .
Sonra devam ettim ve Scala'ya çevirerek daha kısa hale getirdim, burada bir kullanıcının sadece uygulamanın map
ve reduce
bölümlerini belirttiği en basit durumu sağladım . Hadoop / Spark'ta, tam anlamıyla, kullanıcının burada ana hatları verilen 4 işlevi daha açıkça belirtmesini gerektiren daha karmaşık bir programlama modeli kullanılır: http://en.wikipedia.org/wiki/MapReduce#Dataflow
import scalaz.syntax.id._
trait MapReduceModel {
type MultiSet[T] = Iterable[T]
// `map` must be a pure function
def mapPhase[K1, K2, V1, V2](map: ((K1, V1)) => MultiSet[(K2, V2)])
(data: MultiSet[(K1, V1)]): MultiSet[(K2, V2)] =
data.flatMap(map)
def shufflePhase[K2, V2](mappedData: MultiSet[(K2, V2)]): Map[K2, MultiSet[V2]] =
mappedData.groupBy(_._1).mapValues(_.map(_._2))
// `reduce` must be a monoid
def reducePhase[K2, V2, V3](reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)])
(shuffledData: Map[K2, MultiSet[V2]]): MultiSet[V3] =
shuffledData.flatMap(reduce).map(_._2)
def mapReduce[K1, K2, V1, V2, V3](data: MultiSet[(K1, V1)])
(map: ((K1, V1)) => MultiSet[(K2, V2)])
(reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)]): MultiSet[V3] =
mapPhase(map)(data) |> shufflePhase |> reducePhase(reduce)
}
// Kinda how MapReduce works in Hadoop and Spark except `.par` would ensure 1 element gets a process/thread on a cluster
// Furthermore, the splitting here won't enforce any kind of balance and is quite unnecessary anyway as one would expect
// it to already be splitted on HDFS - i.e. the filename would constitute K1
// The shuffle phase will also be parallelized, and use the same partition as the map phase.
abstract class ParMapReduce(mapParNum: Int, reduceParNum: Int) extends MapReduceModel {
def split[T](splitNum: Int)(data: MultiSet[T]): Set[MultiSet[T]]
override def mapPhase[K1, K2, V1, V2](map: ((K1, V1)) => MultiSet[(K2, V2)])
(data: MultiSet[(K1, V1)]): MultiSet[(K2, V2)] = {
val groupedByKey = data.groupBy(_._1).map(_._2)
groupedByKey.flatMap(split(mapParNum / groupedByKey.size + 1))
.par.flatMap(_.map(map)).flatten.toList
}
override def reducePhase[K2, V2, V3](reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)])
(shuffledData: Map[K2, MultiSet[V2]]): MultiSet[V3] =
shuffledData.map(g => split(reduceParNum / shuffledData.size + 1)(g._2).map((g._1, _)))
.par.flatMap(_.map(reduce))
.flatten.map(_._2).toList
}
Harita, bir diziye uygulanabilen yerel bir JS yöntemidir. Orijinal dizideki her öğeye eşlenen bazı işlevlerin bir sonucu olarak yeni bir dizi oluşturur. Dolayısıyla, bir işlevi (öğe) {dönüş öğesi * 2;} eşlediyseniz, her öğe iki katına çıkarılmış yeni bir dizi döndürür. Orijinal dizi değiştirilmez.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Azaltma, bir diziye de uygulanabilen yerel bir JS yöntemidir. Bir diziye bir işlev uygular ve biriktirici adı verilen bir başlangıç çıkış değerine sahiptir. Dizideki her öğe arasında döngü oluşturur, bir işlev uygular ve bunları tek bir değere (toplayıcı olarak başlar) indirger. Kullanışlıdır çünkü istediğiniz herhangi bir çıktıya sahip olabilirsiniz, sadece bu tür bir akümülatörle başlamanız gerekir. Yani bir şeyi bir nesneye indirgemek istersem, bir akümülatörle başlarım {}.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a
Harita indirgeme:
Büyük bir şeyi çalıştırmak için ofisimizdeki farklı bilgisayarın hesaplama gücünü kullanabiliriz. İşin zor kısmı, görevi farklı bilgisayarlar arasında bölmektir.MapReduce kütüphanesi tarafından yapılır.
Temel fikir, işi iki bölüme ayırmanızdır: Harita ve Azaltma. Harita temelde sorunu alır, alt parçalara ayırır ve alt parçaları farklı makinelere gönderir - böylece tüm parçalar aynı anda çalışır. Azaltma, sonuçları alt bölümlerden alır ve tek bir yanıt almak için bunları tekrar birleştirir.
Giriş, kayıtların bir listesidir. Harita hesaplamasının sonucu, anahtar / değer çiftlerinin bir listesidir. Azaltma, aynı anahtara sahip her bir değer kümesini alır ve bunları tek bir değerde birleştirir. İşin 100 parçaya mı yoksa 2 parçaya mı bölündüğünü bilemezsiniz; sonuç, tek bir haritanın sonucuna çok benziyor.
Lütfen basit haritaya bakın ve programı azaltın:
Harita işlevi, orijinal listemize bazı işlevleri uygulamak için kullanılır ve bu nedenle yeni bir liste oluşturulur. Python'daki map () işlevi, bağımsız değişken olarak bir işlevi ve bir listeyi alır. Listenin her öğesine işlev uygulanarak yeni bir liste döndürülür.
li = [5, 7, 4, 9]
final_list = list(map(lambda x: x*x , li))
print(final_list) #[25, 49, 16, 81]
Python'daki azaltma () işlevi, bağımsız değişken olarak bir işlevi ve bir listeyi alır. İşlev, bir lambda işlevi ve bir liste ile çağrılır ve yeni bir indirgenmiş sonuç döndürülür. Bu, listenin çiftleri üzerinde tekrarlayan bir işlem gerçekleştirir.
#reduce func to find product/sum of list
x=(1,2,3,4)
from functools import reduce
reduce(lambda a,b:a*b ,x) #24