web-dev-qa-db-fra.com

Spark: moyen efficace de tester si un RDD est vide

Il n'y a pas de méthode isEmpty sur les RDD, alors quelle est la manière la plus efficace de tester si un RDD est vide?

26
Tobber

RDD.isEmpty() fera partie de Spark 1.3.0.

Sur la base des suggestions de ce fil de discussion Apache et plus tard de quelques commentaires à cette réponse, j'ai fait quelques petites expériences locales. La meilleure méthode consiste à utiliser take(1).length==0.

def isEmpty[T](rdd : RDD[T]) = {
  rdd.take(1).length == 0 
}

Il doit s'exécuter dans O(1) sauf lorsque le RDD est vide, auquel cas il est linéaire dans le nombre de partitions.

Merci à Josh Rosen et Nick Chammas de m'avoir signalé cela.

Remarque: cela échoue si le RDD est de type RDD[Nothing], Par ex. isEmpty(sc.parallelize(Seq())), mais ce n'est probablement pas un problème dans la vie réelle. isEmpty(sc.parallelize(Seq[Any]())) fonctionne très bien.


Modifications:

  • Edit 1: Ajout de la méthode take(1)==0, grâce aux commentaires.

Ma suggestion initiale: Utilisez mapPartitions.

def isEmpty[T](rdd : RDD[T]) = {
  rdd.mapPartitions(it => Iterator(!it.hasNext)).reduce(_&&_) 
}

Il doit évoluer dans le nombre de partitions et n'est pas aussi propre que take(1). Il est cependant robuste aux RDD de type RDD[Nothing].


Expériences:

J'ai utilisé ce code pour les timings.

def time(n : Long, f : (RDD[Long]) => Boolean): Unit = {
  val start = System.currentTimeMillis()
  val rdd = sc.parallelize(1L to n, numSlices = 100)
  val result = f(rdd)
  printf("Time: " + (System.currentTimeMillis() - start) + "   Result: " + result)
}

time(1000000000L, rdd => rdd.take(1).length == 0L)
time(1000000000L, rdd => rdd.mapPartitions(it => Iterator(!it.hasNext)).reduce(_&&_))
time(1000000000L, rdd => rdd.count() == 0L)
time(1000000000L, rdd => rdd.takeSample(true, 1).isEmpty)
time(1000000000L, rdd => rdd.fold(0)(_ + _) == 0L)

time(1L, rdd => rdd.take(1).length == 0L)
time(1L, rdd => rdd.mapPartitions(it => Iterator(!it.hasNext)).reduce(_&&_))
time(1L, rdd => rdd.count() == 0L)
time(1L, rdd => rdd.takeSample(true, 1).isEmpty)
time(1L, rdd => rdd.fold(0)(_ + _) == 0L)

time(0L, rdd => rdd.take(1).length == 0L)
time(0L, rdd => rdd.mapPartitions(it => Iterator(!it.hasNext)).reduce(_&&_))
time(0L, rdd => rdd.count() == 0L)
time(0L, rdd => rdd.takeSample(true, 1).isEmpty)
time(0L, rdd => rdd.fold(0)(_ + _) == 0L)

Sur ma machine locale avec 3 cœurs de travail, j'ai obtenu ces résultats

Time:    21   Result: false
Time:    75   Result: false
Time:  8664   Result: false
Time: 18266   Result: false
Time: 23836   Result: false

Time:   113   Result: false
Time:   101   Result: false
Time:    68   Result: false
Time:   221   Result: false
Time:    46   Result: false

Time:    79   Result: true
Time:    93   Result: true
Time:    79   Result: true
Time:   100   Result: true
Time:    64   Result: true
28
Tobber

Depuis Spark 1. la isEmpty() fait partie de l'API RDD. Un correctif qui provoquait l'échec de isEmpty a été ultérieurement corrigé dans Spark 1.4 .

Pour les DataFrames, vous pouvez faire:

val df: DataFrame = ...
df.rdd.isEmpty()

Voici un extrait du code issu de l'implémentation RDD (à partir de 1.4.1).

  /**
   * @note due to complications in the internal implementation, this method will raise an
   * exception if called on an RDD of `Nothing` or `Null`. This may be come up in practice
   * because, for example, the type of `parallelize(Seq())` is `RDD[Nothing]`.
   * (`parallelize(Seq())` should be avoided anyway in favor of `parallelize(Seq[T]())`.)
   * @return true if and only if the RDD contains no elements at all. Note that an RDD
   *         may be empty even when it has at least 1 partition.
   */
  def isEmpty(): Boolean = withScope {
    partitions.length == 0 || take(1).length == 0
  }
3
marios