Что такое карта / уменьшить? - PullRequest
82 голосов
/ 23 декабря 2008

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

Ответы [ 7 ]

68 голосов
/ 23 декабря 2008

Из аннотации Google MapReduce страница публикации исследования:

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

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

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

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

Смотри также: Википедия: MapReduce

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

16 голосов
/ 23 декабря 2008

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

Вот как карта, вероятно, написана на 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, и Джоэл Спольски любит рассказывать 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;
}

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

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

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

16 голосов
/ 23 декабря 2008

Объяснение MapReduce .

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

2 голосов
/ 16 мая 2014

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

Затем я сделал это более кратким, переведя на 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
}
0 голосов
/ 07 апреля 2019

MapReduce:

Чтобы запустить что-то большое, мы можем использовать вычислительную мощность другого компьютера в нашем офисе. Сложной частью является разделение задачи между компьютерами. Это выполняется библиотекой MapReduce.

Основная идея состоит в том, что вы делите работу на две части: карту и уменьшение. Карта в основном берет на себя задачу, разбивает ее на части и отправляет части на разные машины, поэтому все части выполняются одновременно. Reduce берет результаты из частей и объединяет их вместе, чтобы получить один ответ.

Ввод - это список записей. Результатом вычисления карты является список пар ключ / значение. 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]

Функция redu () в 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
0 голосов
/ 08 сентября 2017

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...