Carte des différences de performances () vs withColumn ()

Oct 21 2020

J'ai une table avec plus de 100 colonnes. Je dois supprimer les guillemets doubles de certaines colonnes. J'ai trouvé 2 façons de le faire, en utilisant withColumn () et map ()

Utilisation de withColumn ()

cols_to_fix = ["col1", ..., "col20"]
for col in cols_to_fix:
    df = df.withColumn(col, regexp_replace(df[col], "\"", ""))

Utilisation de la carte ()

def remove_quotes(row: Row) -> Row:
    row_as_dict = row.asDict()
    cols_to_fix = ["col1", ..., "col20"]
    for column in cols_to_fix:
        if row_as_dict[column]:
            row_as_dict[column] = re.sub("\"", "", str(row_as_dict[column]))
    return Row(**row_as_dict)
 
df = df.rdd.map(remove_quotes).toDF(df.schema)

Voici ma question. J'ai trouvé que l'utilisation de map () prend environ 4 fois plus de temps qu'avec withColumn () sur une table contenant environ 25 millions d'enregistrements. J'apprécierai vraiment si un autre utilisateur de débordement de pile peut expliquer la raison de la différence de performances, afin que je puisse éviter un piège similaire à l'avenir.

Réponses

Colin Oct 21 2020 at 14:28

tout d'abord, un conseil: ne convertissez pas DataFrame en RDD et faites simplement df.map (votre fonction ici), cela peut gagner beaucoup de temps. la page suivantehttps://dzone.com/articles/apache-spark-3-reasons-why-you-should-not-use-rdds nous ferait gagner beaucoup de temps, sa principale conclusion est que RDD est remarquablement lent que DataFrame / Dataset, sans parler du temps utilisé pour la conversion de DataFrame vers RDD.

parlons maintenant de map et withColumn sans aucune conversion entre DataFrame en RDD. Conclusion d'abord: la carte est généralement 5 fois plus lente que withColumn. la raison en est que l'opération de carte implique toujours la désérialisation et la sérialisation tandis que withColumn peut fonctionner sur la colonne d'intérêt. pour être précis, l'opération de carte doit désérialiser la Row en plusieurs parties sur lesquelles l'opération portera,

un exemple ici: supposons que nous ayons un DataFrame qui ressemble à + -------- + ----------- + | language | users_count | + -------- + ----------- + | Java | 20000 | | Python | 100000 | | Scala | 3000 | + -------- + ----------- + alors nous voulons incrémenter toutes les valeurs de la colonne users_count de 1, nous pouvons le faire comme ceci

    df.map(row => {
        val usersCount = row.getInt(1) + 1
        (row.getString(0), usersCount)
}).toDF("language", "users_count_incremented_by_1")

dans le code ci-dessus, nous devons d'abord désérialiser chaque ligne pour extraire les valeurs de la 2ème colonne, après cela nous sortons les valeurs modifiées et les enregistrons en tant que DataFrame (cette étape nécessite la sérialisation de (a, b) dans Row (a, b) puisque DataFrame n'est rien d'autre qu'un DataSet of Rows). pour une explication plus détaillée, consultez l'excellent article suivanthttps://medium.com/@fqaiser94/udfs-vs-map-vs-custom-spark-native-functions-91ab2c154b44

map ne peut pas opérer sur la colonne elle-même mais doit opérer sur les valeurs de la colonne, obtenir les valeurs nécessite une désérialisation, l'enregistrer en tant que dataframe nécessite une sérialisation.

mais map est toujours d'une grande utilité: avec l'aide de la méthode map, les gens pourraient implémenter des opérations très sophistiquées alors que seules des opérations intégrées pourraient être effectuées si nous n'utilisons que withColumn.

Pour résumer, la carte est plus lente mais plus flexible, withColumn est sûrement le plus efficace alors que ses fonctionnalités sont limitées.