ฉันได้ยินเกี่ยวกับแผนที่ / ลดจำนวนมากโดยเฉพาะอย่างยิ่งในบริบทของระบบประมวลผลคู่ขนานของ Google มันคืออะไรกันแน่?
ฉันได้ยินเกี่ยวกับแผนที่ / ลดจำนวนมากโดยเฉพาะอย่างยิ่งในบริบทของระบบประมวลผลคู่ขนานของ Google มันคืออะไรกันแน่?
คำตอบ:
จากบทคัดย่อของหน้าสิ่งพิมพ์งานวิจัยMapReduceของ Google :
MapReduce เป็นรูปแบบการเขียนโปรแกรมและการใช้งานที่เกี่ยวข้องสำหรับการประมวลผลและสร้างชุดข้อมูลขนาดใหญ่ ผู้ใช้ระบุฟังก์ชันแผนที่ที่ประมวลผลคู่คีย์ / ค่าเพื่อสร้างชุดของคู่คีย์ / ค่ากลางและฟังก์ชันลดที่รวมค่ากลางทั้งหมดที่เชื่อมโยงกับคีย์กลางเดียวกัน
ข้อดีของ MapReduce คือการประมวลผลสามารถดำเนินการควบคู่กันบนโหนดการประมวลผลหลายโหนด (เซิร์ฟเวอร์หลายเครื่อง) จึงเป็นระบบที่ปรับขนาดได้ดีมาก
เนื่องจากมันขึ้นอยู่กับรูปแบบการเขียนโปรแกรมเชิงฟังก์ชันขั้นตอนmap
และreduce
แต่ละขั้นตอนจึงไม่มีผลข้างเคียงใด ๆ (สถานะและผลลัพธ์จากแต่ละส่วนย่อยของmap
กระบวนการไม่ขึ้นอยู่กับส่วนย่อยอื่น) ดังนั้นชุดข้อมูลที่ถูกแมปและลดลงจึงสามารถแยกออกจากกันได้ บนโหนดการประมวลผลหลายโหนด
ภาษาโปรแกรมของคุณ Joel สามารถทำสิ่งนี้ได้หรือไม่? ชิ้นกล่าวถึงความเข้าใจในการเขียนโปรแกรมเชิงฟังก์ชันเป็นสิ่งสำคัญใน Google เพื่อสร้าง MapReduce ซึ่งขับเคลื่อนเครื่องมือค้นหา เป็นการอ่านที่ดีมากหากคุณไม่คุ้นเคยกับการเขียนโปรแกรมเชิงฟังก์ชันและวิธีที่อนุญาตให้ใช้โค้ดที่ปรับขนาดได้
ดูเพิ่มเติมที่: Wikipedia: MapReduce
คำถามที่เกี่ยวข้อง: กรุณาอธิบาย mapreduce ง่ายๆ
MapReduce อธิบาย
มันอธิบายได้ดีกว่าสิ่งที่ฉันทำได้ มันช่วย?
แผนที่เป็นฟังก์ชันที่ใช้ฟังก์ชันอื่นกับรายการทั้งหมดในรายการเพื่อสร้างรายการอื่นที่มีค่าส่งคืนทั้งหมด (อีกวิธีหนึ่งในการพูดว่า "ใช้ f กับ x" คือ "เรียก f โดยส่ง x" ดังนั้นบางครั้งการพูดว่า "ใช้" แทนที่จะเป็น "call" จะดีกว่า)
นี่คือวิธีที่อาจเขียนแผนที่ด้วย C # (เรียกSelect
และอยู่ในไลบรารีมาตรฐาน):
public static IEnumerable<R> Select<T, R>(this IEnumerable<T> list, Func<T, R> func)
{
foreach (T item in list)
yield return func(item);
}
ในฐานะที่คุณเป็นเพื่อน Java และ Joel Spolsky ชอบที่จะบอก GROSSLY UNFAIR LIES เกี่ยวกับ Java ที่เส็งเคร็ง (จริงๆแล้วเขาไม่ได้โกหกมันเส็งเคร็ง แต่ฉันพยายามเอาชนะคุณ) นี่คือความพยายามที่หยาบกร้านของฉันที่ เวอร์ชัน Java (ฉันไม่มีคอมไพเลอร์ Java และฉันจำ Java เวอร์ชัน 1.1 ไม่ได้!):
// 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;
}
ฉันมั่นใจว่าสิ่งนี้สามารถปรับปรุงได้หลายล้านวิธี แต่เป็นความคิดพื้นฐาน
ลดเป็นฟังก์ชันที่เปลี่ยนรายการทั้งหมดในรายการให้เป็นค่าเดียว ในการดำเนินการนี้จำเป็นต้องกำหนดฟังก์ชันอื่นfunc
ที่เปลี่ยนสองรายการให้เป็นค่าเดียว func
มันจะทำงานโดยให้ทั้งสองรายการแรกที่จะ จากนั้นผลลัพธ์ของสิ่งนั้นพร้อมกับรายการที่สาม จากนั้นผลลัพธ์ของสิ่งนั้นกับรายการที่สี่ไปเรื่อย ๆ จนกว่ารายการทั้งหมดจะหายไปและเราจะเหลือเพียงค่าเดียว
ใน C # ลดจะถูกเรียกAggregate
และอยู่ในไลบรารีมาตรฐานอีกครั้ง ฉันจะข้ามไปยังเวอร์ชัน Java โดยตรง:
// 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;
}
เวอร์ชัน Java เหล่านี้ต้องการ generics เพิ่ม แต่ฉันไม่รู้วิธีทำใน Java แต่คุณควรจะสามารถผ่านชั้นเรียนภายในที่ไม่ระบุตัวตนได้เพื่อให้ functors:
string[] names = getLotsOfNames();
string commaSeparatedNames = (string)reduce(names,
new IBinaryFunctor {
public object invoke(object arg1, object arg2)
{ return ((string)arg1) + ", " + ((string)arg2); }
}
หวังว่ายาชื่อสามัญจะกำจัดการร่ายได้ เทียบเท่า typesafe ใน C # คือ:
string commaSeparatedNames = names.Aggregate((a, b) => a + ", " + b);
ทำไมถึง "เจ๋ง" ขนาดนี้? วิธีง่ายๆในการแยกการคำนวณขนาดใหญ่ออกเป็นชิ้นเล็ก ๆ เพื่อให้สามารถนำกลับมารวมกันได้หลายวิธีนั้นยอดเยี่ยมเสมอ วิธีที่ Google ใช้แนวคิดนี้คือการทำให้ขนานกันเนื่องจากทั้งแผนที่และลดสามารถใช้ร่วมกันบนคอมพิวเตอร์หลายเครื่องได้
แต่ข้อกำหนดหลักไม่ใช่ว่าภาษาของคุณสามารถใช้ฟังก์ชันเป็นค่าได้ ภาษา OO ใด ๆ ก็ทำได้ ข้อกำหนดที่แท้จริงสำหรับการขนานคือfunc
ฟังก์ชันเล็ก ๆ น้อย ๆ ที่คุณส่งผ่านไปยังแมปและลดจะต้องไม่ใช้หรืออัปเดตสถานะใด ๆ พวกเขาจะต้องส่งคืนค่าที่ขึ้นอยู่กับอาร์กิวเมนต์ที่ส่งไปให้เท่านั้น มิฉะนั้นผลลัพธ์จะถูกทำให้สับสนอย่างสมบูรณ์เมื่อคุณพยายามเรียกใช้ทั้งสิ่งแบบขนาน
หลังจากผิดหวังมากที่สุดกับวาฟเฟิลที่ยาวมากหรือบล็อกโพสต์สั้น ๆ ที่คลุมเครือในที่สุดฉันก็ค้นพบบทความที่กระชับอย่างดีนี้
จากนั้นฉันก็ดำเนินการต่อและทำให้กระชับมากขึ้นโดยการแปลเป็น Scala ซึ่งฉันได้จัดเตรียมกรณีที่ง่ายที่สุดที่ผู้ใช้เพียงระบุmap
และreduce
บางส่วนของแอปพลิเคชัน ใน Hadoop / Spark พูดอย่างชัดเจนว่ามีการใช้โมเดลการเขียนโปรแกรมที่ซับซ้อนมากขึ้นซึ่งต้องการให้ผู้ใช้ระบุฟังก์ชั่นเพิ่มเติมอีก 4 อย่างที่ระบุไว้ที่นี่: 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
}
แผนที่เป็นวิธี JS ดั้งเดิมที่สามารถใช้กับอาร์เรย์ได้ สร้างอาร์เรย์ใหม่อันเป็นผลมาจากฟังก์ชันบางอย่างที่แมปกับทุกองค์ประกอบในอาร์เรย์เดิม ดังนั้นหากคุณแมปฟังก์ชัน (องค์ประกอบ) {return element * 2;} มันจะส่งคืนอาร์เรย์ใหม่โดยทุกองค์ประกอบจะเพิ่มเป็นสองเท่า อาร์เรย์เดิมจะไม่มีการแก้ไข
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
ลดเป็นวิธี JS ดั้งเดิมที่สามารถนำไปใช้กับอาร์เรย์ได้ ใช้ฟังก์ชันกับอาร์เรย์และมีค่าเอาต์พุตเริ่มต้นที่เรียกว่าแอคคูมูเลเตอร์ มันวนผ่านแต่ละองค์ประกอบในอาร์เรย์ใช้ฟังก์ชันและลดให้เป็นค่าเดียว (ซึ่งเริ่มต้นจากตัวสะสม) มันมีประโยชน์เพราะคุณสามารถมีเอาท์พุทที่คุณต้องการได้คุณก็ต้องเริ่มด้วยตัวสะสมประเภทนั้น ดังนั้นถ้าฉันต้องการลดบางสิ่งลงในวัตถุฉันจะเริ่มต้นด้วยตัวสะสม {}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a
MapReduce:
ในการเรียกใช้สิ่งที่มีขนาดใหญ่เราสามารถใช้พลังการคำนวณของคอมพิวเตอร์เครื่องอื่นในสำนักงานของเรา ส่วนที่ยากคือการแบ่งงานระหว่างคอมพิวเตอร์เครื่องอื่นทำโดยห้องสมุด MapReduce
แนวคิดพื้นฐานคือคุณแบ่งงานออกเป็นสองส่วนคือแผนที่และส่วนลด โดยทั่วไปแผนที่จะใช้ปัญหาแบ่งออกเป็นส่วนย่อยและส่งชิ้นส่วนย่อยไปยังเครื่องจักรต่างๆดังนั้นทุกชิ้นจึงทำงานพร้อมกัน ลดนำผลลัพธ์จากส่วนย่อยและรวมกลับเข้าด้วยกันเพื่อให้ได้คำตอบเดียว
อินพุตคือรายการของบันทึกผลลัพธ์ของการคำนวณแผนที่คือรายการคู่คีย์ / ค่า Reduce นำชุดของค่าแต่ละชุดที่มีคีย์เดียวกันมารวมกันเป็นค่าเดียวคุณไม่สามารถบอกได้ว่างานนั้นแบ่งออกเป็น 100 ชิ้นหรือ 2 ชิ้น ผลลัพธ์สุดท้ายดูเหมือนผลของแผนที่เดียว
โปรดดูแผนที่อย่างง่ายและลดโปรแกรม:
ฟังก์ชั่นแผนที่ใช้เพื่อใช้ฟังก์ชันบางอย่างในรายการเดิมของเราและรายการใหม่จึงถูกสร้างขึ้น ฟังก์ชัน map () ใน Python ใช้ฟังก์ชันและรายการเป็นอาร์กิวเมนต์ รายการใหม่จะถูกส่งคืนโดยใช้ฟังก์ชันกับแต่ละรายการของรายการ
li = [5, 7, 4, 9]
final_list = list(map(lambda x: x*x , li))
print(final_list) #[25, 49, 16, 81]
ฟังก์ชันลด () ใน Python ใช้ฟังก์ชันและรายการเป็นอาร์กิวเมนต์ ฟังก์ชันถูกเรียกด้วยฟังก์ชันแลมบ์ดาและรายการและผลลัพธ์ที่ลดลงใหม่จะถูกส่งกลับ การดำเนินการนี้จะดำเนินการซ้ำ ๆ กับคู่ของรายการ
#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