Pourquoi Rust devrait devenir un langage de programmation fonctionnel

Bonjour, Habr!

AprĂšs avoir commencĂ© Ă  Ă©tudier Scala, je suis immĂ©diatement tombĂ© sur le fait que l'implĂ©mentation fonctionnelle de l'algorithme de tri rapide le plus simple fonctionne radicalement plus lentement et consomme beaucoup plus de mĂ©moire qu'une implĂ©mentation impĂ©rative similaire. En mĂȘme temps, personne ne conteste que le code fonctionnel soit plus concis, expressif et rĂ©sistant aux erreurs. En rĂ©Ă©crivant les deux exemples dans Rust, j'ai dĂ©couvert plusieurs choses importantes que je veux partager. DĂ©tails sous la coupe, et ici je ne donnerai que de brĂšves conclusions:

  1. Pour une mise en Ɠuvre impĂ©rative - le gain de Rust n'Ă©tait que de 20%. Cela signifie que la JVM est proche des performances natives et qu'il n'y a plus rien Ă  amĂ©liorer.
  2. Pour une implĂ©mentation fonctionnelle, Rust s'est avĂ©rĂ© ĂȘtre 4,5 fois plus rapide, la consommation de mĂ©moire a diminuĂ© de 5,5 fois, et l'absence d'un garbage collector a rendu le programme plus stable (moins de variation de performances). C'est intĂ©ressant pour ceux qui veulent Ă©crire des programmes fonctionnels rapides.
  3. Le concept de propriĂ©taire unique des donnĂ©es (et la seule rĂ©fĂ©rence mutable) adoptĂ© dans Rust est trĂšs similaire au concept d'immuabilitĂ©, Ă  la suite duquel les algorithmes fonctionnels basĂ©s sur l'immuabilitĂ©, la rĂ©cursivitĂ© et la copie tombent facilement sur Rust sans pratiquement rĂ©Ă©criture, tandis que les algorithmes impĂ©ratifs forcent le code Ă  ĂȘtre repensĂ©. considĂ©rer la mutabilitĂ© des liens, les durĂ©es de vie, etc.

Conclusion - Rust semble avoir été spécialement créé pour le FP, bien que les possibilités de sa syntaxe ne soient pas encore accessibles à Scala.

Commençons donc avec Scala et implémentons le tri rapide en 2 styles:

ImpĂ©rativement - nous utilisons le mĂȘme tableau, en rĂ©organisant les Ă©lĂ©ments qu'il contient Ă  l'aide d'une procĂ©dure rĂ©cursive. Nous voyons du code verbeux, potentiellement vulnĂ©rable aux fautes de frappe, mais utilisant de maniĂšre optimale la mĂ©moire, et aussi rapidement que possible.

def sort_imp(arr: Array[Double]) {
  def swap(i: Int, j: Int) {
    val t = arr(i)
    arr(i) = arr(j)
    arr(j) = t
  }

  def sort1(l: Int, r: Int) {
    val pivot = arr((l + r) / 2)
    var i = l
    var j = r
    while (i <= j) {
      while (arr(i) < pivot) i += 1
      while (arr(j) > pivot) j -= 1
      if (i <= j) {
        swap(i, j)
        i += 1
        j -= 1
      }
    }
    if (l < j) sort1(l, j)
    if (i < r) sort1(i, r)
  }

  if (arr.length > 1)
    sort1(0, arr.length - 1)
}

— , , . , , (, GC).

def sort_fun(arr: Array[Double]): Array[Double] = {
  if (arr.length <= 1) 
    arr
  else {
    val pivot = arr(arr.length / 2)
    Array.concat(
      sort_fun(arr filter (pivot > _)),
      arr filter (pivot == _),
      sort_fun(arr filter (pivot < _))
    )
  }
}

($ sbt run) 10 . :

  • — 2.5
  • — 40

, java 3.6 .

UPD
LLVM nativeMode := «release» «immix gc», :

  • — 2.3
  • — 63

Rust:

— , , , , , . , borrow-checker.

fn sort_imp(arr: &mut Vec<f64>) {
  fn swap(arr: &mut Vec<f64>, i: usize, j: usize) {
    let t = arr[i];
    arr[i] = arr[j];
    arr[j] = t;
  };

  fn sort1(arr: &mut Vec<f64>, l: usize, r: usize) {
    let pivot = arr[(l + r) / 2];
    let mut i = l;
    let mut j = r;
    while i <= j {
      while arr[i] < pivot { i += 1; }
      while arr[j] > pivot { j -= 1; }
      if i <= j {
        swap(arr, i, j);
        i += 1;
        j -= 1;
      }
    }
    if l < j { sort1(arr, l, j); }
    if i < r { sort1(arr, i, r); }
  };

  if arr.len() > 1 {
    sort1(arr, 0, arr.len() - 1);
  }
}

— , , Rust , .

iter() filter() , x &&f64, **. . append() , , .

fn sort_fun(arr: Vec<f64>) -> Vec<f64> {
  if arr.len() <= 1 {
    arr
  } else {
    let pivot = arr[arr.len() / 2];
    let mut a = Vec::<f64>::with_capacity(arr.len());
    a.append(&mut sort_fun(arr.iter().filter(|x| pivot > **x).cloned().collect()));
    a.append(&mut arr.iter().filter(|x| pivot == **x).cloned().collect());
    a.append(&mut sort_fun(arr.iter().filter(|x| pivot < **x).cloned().collect()));
    a
  }
}

UPD
:

fn sort_fun(arr: Vec<f64>) -> Vec<f64> {
    if arr.len() <= 1 {
        arr
    } else {
        let pivot = arr[arr.len() / 2];
        [
            sort_fun(arr.iter().filter(|&&x| pivot > x).cloned().collect()),
            arr.iter().filter(|&&x| pivot == x).cloned().collect(),
            sort_fun(arr.iter().filter(|&&x| pivot < x).cloned().collect()),
        ]
        .concat()
    }
}

, — iter().filter(...).chain(...). zero-cost, , , — . , .

— , — , ( ).

($ cargo build --release):

  • — 2
  • — 9

— 650 .

:

, — , . , — , Rust -, , Rust , (, null ).

-, zero-cost — . Rust — - . , 2019 Scala Rust .

Scala.
Rust.

.

Source: https://habr.com/ru/post/fr482318/


All Articles