Что такое Map / Reduce?


Я много слышу о map / reduce, особенно в контексте массово параллельной вычислительной системы Google. Что именно это такое?

6 82

6 ответов:

из аннотации Google MapReduce научные публикации, страницы:

MapReduce-это модель программирования и связанная реализация для обработка и генерация больших данных наборы. Пользователи задают функцию карты это обрабатывает пару ключ / значение создать набор промежуточных пары ключ / значение и функция уменьшения что объединяет все промежуточные значения связанный с тем же промежуточным звеном ключ.

В преимущество MapReduce заключается в том, что обработка может выполняться параллельно на нескольких узлах обработки (нескольких серверах), поэтому это система, которая может очень хорошо масштабироваться.

Так как он основан на функциональное программирование модель map и reduce шаги каждый не имеют никаких побочных эффектов (состояние и результаты из каждого подраздела a map процесс не зависит от другого), поэтому сопоставляемый и сокращаемый набор данных может быть разделен на несколько узлы обработки.

Иоиля Может Ли Ваш Язык Программирования Сделать Это? часть обсуждает, как понимание функционального программирования было важно в Google, чтобы придумать MapReduce, который приводит в действие свою поисковую систему. Это очень хорошо читать, если вы не знакомы с функциональным программированием и, насколько это позволяет масштабируемый код.

Читайте также: Википедия: MapReduce

вопрос: пожалуйста, объясните mapreduce просто

Map-это функция, которая применяет другую функцию ко всем элементам списка, чтобы создать другой список со всеми возвращаемыми значениями на нем. (Другой способ сказать "применить f К x "- это"вызвать f, передав его x". Поэтому иногда лучше сказать " применить "вместо"позвонить".)

вот как карта, вероятно, написана на 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 чувак, и Джоэл Сполски любит говорить грубую несправедливую ложь о как дерьмовая 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;
}

Я уверен, что это можно улучшить миллионом способов. Но это основная идея.

Reduce-это функция, которая превращает все элементы списка в одно значение. Для этого ему нужно дать другую функцию func что превращает два элемента в a одно значение. Это будет работать, давая первые два пункта 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 нуждаются в добавлении к ним дженериков, но я не знаю, как это сделать в Java. Но вы должны быть в состоянии передать им анонимные внутренние классы, чтобы обеспечить функторы:

string[] names = getLotsOfNames();

string commaSeparatedNames = (string)reduce(names, 
   new IBinaryFunctor {
       public object invoke(object arg1, object arg2)
           { return ((string)arg1) + ", " + ((string)arg2); }
   }

надеюсь, дженерики избавятся от слепков. Типобезопасный эквивалент в C#:

string commaSeparatedNames = names.Aggregate((a, b) => a + ", " + b);

почему это "круто"? Простые способы разбиения больших вычислений на более мелкие части, чтобы их можно было собрать вместе по-разному, всегда круты. То, что Google применяет эту идею для распараллеливания, так как map и reduce могут быть разделены на несколько компьютеры.

но ключевое требование заключается не в том, что ваш язык может обрабатывать функции как значения. Любой язык OO может это сделать. Фактическое требование для распараллеливания заключается в том, что мало func функции, которые вы передаете в map и reduce, не должны использовать или обновлять какое-либо состояние. Они должны возвращать значение, зависящее только от переданных им аргументов. В противном случае результаты будут полностью испорчены, когда вы попытаетесь запустить все это параллельно.

MapReduce, Которая Пояснила.

Это объясняет лучше, чем я могу. Это помогает?

после получения наиболее разочарованы либо очень долго waffley или очень короткие расплывчатые сообщения в блоге я в конечном итоге обнаружил это очень хороший строгий лаконичный документ.

затем я пошел вперед и сделал его более кратким, переведя на 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
}

Map-это собственный метод JS, который может быть применен к массиву. Он создает новый массив в результате некоторой функции, сопоставленной каждому элементу исходного массива. Поэтому, если вы сопоставили функцию (элемент) { return element * 2;}, он вернет новый массив с каждым удвоенным элементом. Исходный массив останется неизменным.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Reduce-это собственный метод JS это также может быть применено к массиву. Она применяет функцию к массиву и имеет начальное значение на выходе аккумулятора. Он проходит через каждый элемент в массиве, применяет функцию и уменьшает их до одного значения (которое начинается как аккумулятор). Это полезно, потому что вы можете иметь любой выход, который вы хотите, вы просто должны начать с этого типа аккумулятора. Поэтому, если бы я хотел уменьшить что-то в объект, я бы начал с аккумулятора {}.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a