Julia 初探,及与 C++,Java,Python 的比较

第一次听说 Julia 是 2019 年与其他人关于编程的聊天中,再后来看到 Julia 是在一些比较好的论文中。今天真正试了试,发现 Julia 的确兼顾了 C++ 的运算速度与 Python 的简洁。

我用 Julia, C++, Java, Python 运行了一个多阶段报童模型的动态规划,运算速度如下:

Julia

planning horizon = 40

runtime = 0.6730000972747803 sec

optimal value = 1359.2762353303274

C++

planning horizon is 40 periods

running time of C++ is 0.313397s

Final optimal value is: 1359.28

Java

planning horizon is 40 periods

running time of Java is 1.805 s

final optimal expected value is: 1357.1966898556248

Python

planning horizon = 40

runtime of Python = 40.8762 s

optimal value = 1359.2762353303237

  • Julia 的代码量只有 C++ 的 2/3,但是速度差不太多,比 java 还快,比 python 更是快不少。
  • Julia 还有交互式编辑器 Pluto,类似 Jupyter notebook,这个用来演示或画动态图很方便

下面是 julia 代码:

Julia 复制代码
using Statistics
using Distributions


function poisson_pmf(k::Int, λ::Float64)
    if k < 0 || λ < 0
        return 0.0
    elseif k == 0 && λ == 0
        return 1.0
    end
    logp = -λ + k * log(λ) - lgmma(k + 1)
    return exp(logp)
end

function poisson_quantile(p::Float64, λ::Float64)
    low = 0
    high = max(100, Int(3λ))
    while low < high
        mid = (low + high) ÷ 2
        if cdf(Poisson(λ), mid) < p
            low = mid + 1
        else
            high = mid
        end
    end
    return low
end

# --------------------------
# PMF truncation
# --------------------------

function get_pmf_poisson(demands::Vector{Float64}, q::Float64)
    T = length(demands)
    pmf = Vector{Vector{Tuple{Int, Float64}}}(undef, T)

    for t in 1:T
        ub = poisson_quantile(q, demands[t])
        lb = poisson_quantile(1 - q, demands[t])

        support = [(d, pdf(Poisson(demands[t]), d) / (2q - 1))
                   for d in lb:ub]

        pmf[t] = support
    end

    return pmf
end

# --------------------------
# State
# --------------------------

struct State
    period::Int
    inventory::Float64
end

Base.:(==)(a::State, b::State) = a.period == b.period && a.inventory == b.inventory
Base.hash(s::State, h::UInt) = hash((s.period, s.inventory), h)

# --------------------------
# Newsvendor DP
# --------------------------
# mutable 表示这个结构体的字段是可以被修改的
mutable struct NewsvendorDP
    T::Int
    capacity::Float64
    stepSize::Float64
    fix_cost::Float64
    var_cost::Float64
    hold_cost::Float64
    penalty_cost::Float64
    max_I::Float64
    min_I::Float64
    pmf::Vector{Vector{Tuple{Int, Float64}}}
    cache_actions::Dict{State, Float64}
    cache_values::Dict{State, Float64}
end

function feasible_actions(model::NewsvendorDP)
    Q = Int(model.capacity / model.stepSize)
    return [i * model.stepSize for i in 0:Q-1]
end

function transition(model::NewsvendorDP, s::State, a, d)
    nextI = s.inventory + a - d
    nextI = clamp(nextI, model.min_I, model.max_I)
    return State(s.period + 1, nextI)
end

function immediate_cost(model::NewsvendorDP, s::State, a, d)
    fix = a > 0 ? model.fix_cost : 0.0
    vari = a * model.var_cost

    nextI = clamp(s.inventory + a - d, model.min_I, model.max_I)

    hold = max(model.hold_cost * nextI, 0.0)
    penalty = max(-model.penalty_cost * nextI, 0.0)

    return fix + vari + hold + penalty
end

function recursion(model::NewsvendorDP, s::State)
    if haskey(model.cache_values, s)
        return model.cache_values[s]
    end

    best_val = Inf
    best_q = 0.0

    for a in feasible_actions(model)
        val = 0.0

        for (d, p) in model.pmf[s.period]
            val += p * immediate_cost(model, s, a, d)

            if s.period < model.T
                ns = transition(model, s, a, d)
                val += p * recursion(model, ns)
            end
        end

        if val < best_val
            best_val = val
            best_q = a
        end
    end

    model.cache_actions[s] = best_q
    model.cache_values[s] = best_val

    return best_val
end

# --------------------------
# main
# --------------------------

function main()
    T = 40
    mean_demand = 20.0
    demands = fill(mean_demand, T)

    capacity = 150.0
    stepSize = 1.0
    fix_cost = 0.0
    var_cost = 1.0
    hold_cost = 2.0
    penalty_cost = 10.0

    trunc_q = 0.9999
    maxI = 100.0
    minI = -100.0

    pmf = get_pmf_poisson(demands, trunc_q)

    model = NewsvendorDP(
        T, capacity, stepSize,
        fix_cost, var_cost,
        hold_cost, penalty_cost,
        maxI, minI, pmf,
        Dict(), Dict()
    )

    s0 = State(1, 0.0)

    t0 = time()
    val = recursion(model, s0)
    t1 = time()

    println("planning horizon = $T")
    println("runtime = $(t1 - t0) sec")
    println("optimal value = $val")
end


main()

对应的C++代码:

cpp 复制代码
/*
 * Created by Zhen Chen on 2025/12/24.
 * Email: chen.zhen5526@gmail.com
 * Description: vanilla version of DP for newsvendor;
 *
 */

#include <array>
#include <boost/functional/hash.hpp>
#include <chrono>
#include <iostream>
#include <limits>
#include <unordered_map>

double poissonCDF(const int k, const double lambda) {
  double cumulative = 0.0;
  double term = std::exp(-lambda); // P(X=0)
  for (int i = 0; i <= k; ++i) {
    cumulative += term;
    if (i < k)
      term *= lambda / (i + 1); // 递推计算 P(X=i)
  }

  return cumulative;
}

double poissonPMF(const int k, const int lambda) {
  if (k < 0 || lambda < 0)
    return 0.0; // 确保参数合法
  if (k == 0 and lambda == 0)
    return 1.0;
  // lgamma 对 tgamma 取 ln
  const double logP = -lambda + k * std::log(lambda) - std::lgamma(k + 1);
  return std::exp(logP); // Use the logarithmic form to avoid overflow from std::tgamma(k + 1)
}

int poissonQuantile(const double p, const double lambda) {
  int low = 0, high = std::max(100, static_cast<int>(lambda * 3)); // 初始搜索区间
  while (low < high) {
    if (const int mid = (low + high) / 2; poissonCDF(mid, lambda) < p) {
      low = mid + 1;
    } else {
      high = mid;
    }
  }
  return low;
}

std::vector<std::vector<std::array<double, 2>>> getPMFPoisson(const std::vector<double> &demands,
                                                              const double truncated_quantile) {
  const size_t T = demands.size();
  std::vector<int> support_lb(T);
  std::vector<int> support_ub(T);
  for (size_t i = 0; i < T; ++i) {
    support_ub[i] = poissonQuantile(truncated_quantile, demands[i]);
    support_lb[i] = poissonQuantile(1 - truncated_quantile, demands[i]);
  }
  std::vector pmf(T, std::vector<std::array<double, 2>>());
  for (int t = 0; t < T; ++t) {
    const int demand_length = static_cast<int>((support_ub[t] - support_lb[t] + 1));
    pmf[t].resize(demand_length, std::array<double, 2>());
    for (int j = 0; j < demand_length; ++j) {
      pmf[t][j][0] = support_lb[t] + j;
      const int demand = static_cast<int>(pmf[t][j][0]);
      pmf[t][j][1] =
          poissonPMF(demand, static_cast<int>(demands[t])) / (2 * truncated_quantile - 1);
    }
  }
  return pmf;
}

class State {
  int period{}; // c++11, {} 值初始化,默认为 0
  double ini_inventory{};

public:
  State() {}

  explicit State(const int period, const double ini_inventory)
      : period(period), ini_inventory(ini_inventory) {};

  [[nodiscard]] double get_ini_inventory() const { return ini_inventory; }

  [[nodiscard]] int getPeriod() const { return period; }

  // for unordered map
  bool operator==(const State &other) const {
    return period == other.period && ini_inventory == other.ini_inventory;
  }

  friend struct std::hash<State>;

  // for ordered map
  bool operator<(const State &other) const {
    if (period != other.period)
      return period < other.period;
    if (ini_inventory != other.ini_inventory)
      return ini_inventory < other.ini_inventory;
    return false;
  }

  friend std::ostream &operator<<(std::ostream &os, const State &state);
};

template <> struct std::hash<State> {
  // size_t 表示无符号整数
  size_t operator()(const State &s) const noexcept {
    // noexcept 表示这个函数不会抛出异常
    // boost 的哈希计算更安全
    std::size_t seed = 0;
    boost::hash_combine(seed, s.period);
    boost::hash_combine(seed, s.ini_inventory);
    return seed;
  }
};

class NewsvendorDP {
  size_t T;
  double capacity;
  double fix_order_cost;
  double unit_vari_order_cost;
  double unit_hold_cost;
  double unit_penalty_cost;
  double truncated_quantile;
  double max_I;
  double min_I;
  std::vector<std::vector<std::array<double, 2>>> pmf;
  bool parallel{};
  bool compute_Gy = false;
  State ini_state{};
  std::unordered_map<State, double> cache_actions;
  std::unordered_map<State, double> cache_values;

public:
  NewsvendorDP(const size_t T, const double capacity, const double fix_order_cost,
               const double unit_vari_order_cost, const double unit_hold_cost,
               const double unit_penalty_cost, const double truncated_quantile, const double max_I,
               const double min_I, const std::vector<std::vector<std::array<double, 2>>> &pmf)
      : T(static_cast<int>(T)), capacity(capacity), fix_order_cost(fix_order_cost),
        unit_vari_order_cost(unit_vari_order_cost), unit_hold_cost(unit_hold_cost),
        unit_penalty_cost(unit_penalty_cost), truncated_quantile(truncated_quantile), max_I(max_I),
        min_I(min_I), pmf(pmf) {};

  [[nodiscard]] std::vector<double> feasibleActions() const {
    const int QNum = static_cast<int>(capacity);
    std::vector<double> actions(QNum);
    for (int i = 0; i < QNum; i = i + 1) {
      actions[i] = i;
    }
    return actions;
  }

  [[nodiscard]] State stateTransitionFunction(const State &state, const double action,
                                              const double demand) const {
    double nextInventory = state.get_ini_inventory() + action - demand;

    nextInventory = nextInventory > max_I ? max_I : nextInventory;
    nextInventory = nextInventory < min_I ? min_I : nextInventory;

    const int nextPeriod = state.getPeriod() + 1;
    // C++11 引入了统一的列表初始化(Uniform Initialization),鼓励使用大括号 {} 初始化类
    const auto newState = State{nextPeriod, nextInventory};

    return newState;
  }

  [[nodiscard]] double immediateValueFunction(const State &state, const double action,
                                              const double demand) const {
    const double fixCost = action > 0 ? fix_order_cost : 0;
    const double variCost = action * unit_vari_order_cost;
    double nextInventory = state.get_ini_inventory() + action - demand;
    nextInventory = nextInventory > max_I ? max_I : nextInventory;
    nextInventory = nextInventory < min_I ? min_I : nextInventory;
    const double holdCost = std::max(unit_hold_cost * nextInventory, 0.0);
    const double penaltyCost = std::max(-unit_penalty_cost * nextInventory, 0.0);

    const double totalCost = fixCost + variCost + holdCost + penaltyCost;
    return totalCost;
  }

  double recursion(const State &state) { // NOLINT
    double bestQ = 0.0;
    double bestValue = std::numeric_limits<double>::max();
    const std::vector<double> actions = feasibleActions(); // should not move inside
    for (const double action : actions) {
      double thisValue = 0;
      for (auto demandAndProb : pmf[state.getPeriod() - 1]) {
        thisValue += demandAndProb[1] * immediateValueFunction(state, action, demandAndProb[0]);
        if (state.getPeriod() < T) {
          auto newState = stateTransitionFunction(state, action, demandAndProb[0]);
          auto it = cache_values.find(newState);
          if (it != cache_values.end()) {
            thisValue += demandAndProb[1] * it->second;
          } else {
            thisValue += demandAndProb[1] * recursion(newState);
          }
        }
      }
      if (thisValue < bestValue) {
        bestValue = thisValue;
        bestQ = action;
      }
    }
    cache_actions[state] = bestQ;
    cache_values[state] = bestValue;
    return bestValue;
  }
};

int main() {
  constexpr int T = 40;
  constexpr double mean_demand = 20;
  const std::vector demands(T, mean_demand);

  constexpr double capacity = 150; // maximum ordering quantity
  constexpr double fix_order_cost = 0;
  constexpr double unitVariOderCost = 1;
  constexpr double unit_hold_cost = 2;
  constexpr double unit_penalty_cost = 10;
  constexpr double truncQuantile = 0.9999; // truncated quantile for the demand distribution
  constexpr double maxI = 100;             // maximum possible inventory
  constexpr double minI = -100;            // minimum possible inventory

  const auto pmf = getPMFPoisson(demands, truncQuantile);
  auto model = NewsvendorDP(T, capacity, fix_order_cost, unitVariOderCost, unit_hold_cost,
                            unit_penalty_cost, truncQuantile, maxI, minI, pmf);

  const auto initialState = State(1, 0);
  const auto start_time = std::chrono::high_resolution_clock::now();
  const auto optValue = model.recursion(initialState);
  const auto end_time = std::chrono::high_resolution_clock::now();
  const std::chrono::duration<double> duration = end_time - start_time;
  std::cout << "planning horizon is " << T << " periods" << std::endl;
  std::cout << "running time of C++ is " << duration << std::endl;
  std::cout << "Final optimal value is: " << optValue << std::endl;
  return 0;
}
相关推荐
一叶飘零_sweeeet2 小时前
优秀文章合集
java
ZC跨境爬虫2 小时前
3D 地球卫星轨道可视化平台开发 Day8(分步渲染200颗卫星+ 前端分页控制)
前端·python·3d·重构·html
zopple2 小时前
ThinkPHP5.x与3.x核心差异解析
java·python·php
2401_835956812 小时前
Golang怎么写基准测试benchmark_Golang基准测试教程【完整】
jvm·数据库·python
小欣加油2 小时前
leetcode2078 两栋颜色不同且距离最远的房子
数据结构·c++·算法·leetcode·职场和发展
我真不是小鱼2 小时前
cpp刷题打卡记录30——轮转数组 & 螺旋矩阵 & 搜索二维矩阵II
数据结构·c++·算法·leetcode
南境十里·墨染春水2 小时前
C++ 笔记 thread
java·开发语言·c++·笔记·学习
南境十里·墨染春水2 小时前
C++ 笔记 高级线程同步原语与线程池实现
java·开发语言·c++·笔记·学习
lkforce2 小时前
MiniMind学习笔记(二)--model_minimind.py
笔记·python·学习·minimind·minimindconfig