函数式,F#都做了哪些优化?

2023-11-01

非函数式语言中使用函数式风格的缺点:

函数式的优点,想必大家都已经非常了解了。我们来看看,一般语言使用函数式风格可能带来的问题:

  1. 变量默认是可变的。为了实现不可变性,开发者只能人为的规范不去改变变量的值,没有明确的变量修改提示,容易因失误改变变量的值。
  2. 为了实现不可变性,往往需要更多的变量来存储中间结果,产生大量的资源开销。又由于垃圾回收往往不是即时的,随着数据规模上升,可能会出现资源消耗问题。
  3. 使用递归来替代循环,如果语言没有对尾递归做优化,每次递归调用会变成真实的函数调用,影响性能。并且如果递归函数中有大量临时变量,还可能导致堆栈空间被快速侵蚀,引起性能和资源占用双重问题。
  4. 为了实现高阶函数,需要支持函数作为参数。在函数内调用函数如果不能做到内联,也会产生不必要的性能开销。如果函数调用在循环中,这些开销会累积从而影响性能。
  5. 柯里化的函数,需要将每个参数都包装成一个独立的函数,然后利用闭包进行函数的嵌套。这导致了过度包装,调用时则是层层解包,影响性能。
  6. 函数式的序列处理,如果不支持lazy模式,则会产生许多不必要的额外处理。这在结合短路模式时尤为明显。例如调用了map函数,再调用exists函数,map会先完成完整遍历得到全新的序列,然后交给exists做短路判断,影响性能。

F#如何解决上述问题

值默认不可变

为了解决第一个缺点,F#对值的可变性做了强制限制。

F#的值默认是不可变的。虽然你仍然可以使用mutable关键词将其设置为变量,但即使是可变的变量也采用了不同的赋值符号(<-)区别于绑定符号(=)。这样可以很大程度上减少犯错。

看如下例子:

let a = 1
a <- 2  //这将导致编译器报错,因为a不可变
a = 2  //只在let绑定中=才是绑定值,其余位置=是比较符,相当于==。因此此处是比较a与2的值,该表达式返回bool类型。很难用错。

尾递归优化和参数变量化

为了解决2和3,F#针对尾递归做了优化,并且将递归的参数编译为变量,递归的调用实际上被编译为了循环体内变量的更替。

尾递归函数的特点是:不保存任何中间值,下层递归结果不参与本层函数的计算,在本层函数的某些分支的终点(最后一句)调用递归。

判断一个递归函数是不是尾递归,有十分简单的规则:

看递归是不是会在最后一层返回该函数的某个参数(或其运算)作为结果,并且此次返回会一路返回到最上层,不会参与到中间任何一层的运算中。

尾递归,很容易总结出以下性质:

  1. 尾递归过程中的所有变化值都在参数中。
  2. 尾递归最重要返回的值也在参数中
  3. 尾递归除了参数之外没有任何新增的变量需要在进入下一层递归时保留。

因此,尾递归可以简单的优化成while循环。而出口点则在所有没有调用递归的分支上(相当于break)。所有有递归调用的分支,递归调用可以翻译为对参数对应的几个变量进行赋值,然后再次进入循环。

比如下面的例子:

//求1..i的和
let rec sum s i =  //s存储的是和, s和i被编译为变量
	match i with
	| 0 -> s //终止条件,相当于break,最终返回s
	| _ -> 
		sum (s + i) (i - 1)  //递归调用,被编译为s <- s + i,i <- i - 1,然后继续循环。当然为了考虑变量变更后值的问题,实际上还引入了临时变量,这里不展开了,可以看我上一篇文章。

sum 0 100  //计算从1到100的和

上述代码,差不多会被优化成:(示意,并不精确)

int s = 0;
int i = 100;
while (true)
{
    if (i == 0) break;
    s += i;
    i--;
}

从而既解决了递归嵌套的问题,又解决了临时变量的创建和释放问题。

FSharpFun<>

为了解决4和5:

F#中定义的函数是自动柯里化的。对于大于一个参数的柯里化的函数定义,F#采用了专门的泛型类来描述,那就是FSharpFun。同样如果函数作为参数,它的类型也是FSharpFun。其内部针对柯里化做出了优化,使得柯里化的函数可以用过InvokeFast方法进行优化调用。如果该函数调用存在优化版本,则相当于只调用了一次Invoke,从而避免了Invoke之后再Invoke的连锁调用。FSharpFun通过泛型来定义每个参数和返回值的类型。从而解决了柯里化的过度包装和性能消耗问题。这么做的副作用是只能支持有限个数的柯里化参数的函数,不过这不是什么大问题。

在F#中,函数可以被显式的声明为inline,从而可以内联到其他函数中。在F#6.0中,还引入了InlineIfLambda特性,来标记参数,使得lambda描述的匿名方法可以自动内联。不过该特性只能用在类的方法上,而不能用于一般的let绑定的函数。

Seq和Lazy

解决6其实是手到擒来的事情。大家都知道.Net架构有迭代器,而迭代器就是Lazy的,具有延时执行的特点。F#中针对序列处理的模块Seq,就等同于.Net中的IEnumerable。而Seq模块的函数,其本质就是迭代器。因而Seq的函数大都具有lazy的特性。

这里最好直接拿一个样例来举例,该例子中,takeWhile是取元素,直到不满足条件,exists是遍历直到出现第一个满足条件的。两个函数都是短路的。我们看看F#如何运行,会不会正确的短路掉。

//求n以内的质数
let primes n =
    let rec loop list i =
        match i with
        | _ when i > n ->
            list
        | _ ->
            let h = float i |> sqrt |> int
            list
            |> Seq.takeWhile (fun j -> j <= h)
            |> Seq.exists (fun j -> i % j = 0)
            |> function
                | true -> loop list (i + 1)
                | false -> loop (list @ [i]) (i + 1)
    loop [] 2

printfn "%A" (primes 100)

上面的代码打印了100以内的质数。其中list存储了递归至今的质数,i是当前测试的数。h是i的平方根。针对i要检测list中是否有可以整除的元素,遍历list的时候在元素大于h时应当终止本轮遍历并返回false(无整除元素),遍历到任何可以整除的元素时应当返回true。这两个短路条件返回的一个是true一个是false,因而无法写进同一个短路函数中。takeWhile和exists就作为两个短路函数先后调用了。

我们使用Seq的函数,实现lazy的运行。为了展示具体执行过程,我们添加一些打印内容。修改代码如下:

let primes n =
    let rec loop list i =
        match i with
        | _ when i > n ->
            list
        | _ ->
            printf "lv.%d --- " i
            let h = float i |> sqrt |> int
            list
            |> Seq.takeWhile (fun j -> printf "A-%d; " j; j <= h)
            |> Seq.exists (fun j -> printf "B-%d; " j; i % j = 0)
            |> function
                | true -> printfn ""; loop list (i + 1)
                | false -> printfn ""; loop (list @ [i]) (i + 1)
    loop [] 2

printfn "%A" (primes 100)

可以看出,仅仅增加了一下打印输出而已。我们看看执行结果:

lv.2 ---
lv.3 --- A-2;
lv.4 --- A-2; B-2;
lv.5 --- A-2; B-2; A-3;
lv.6 --- A-2; B-2;
lv.7 --- A-2; B-2; A-3;
lv.8 --- A-2; B-2;
lv.9 --- A-2; B-2; A-3; B-3;
lv.10 --- A-2; B-2;
lv.11 --- A-2; B-2; A-3; B-3; A-5;
lv.12 --- A-2; B-2;
lv.13 --- A-2; B-2; A-3; B-3; A-5;
lv.14 --- A-2; B-2;
lv.15 --- A-2; B-2; A-3; B-3;
lv.16 --- A-2; B-2;
lv.17 --- A-2; B-2; A-3; B-3; A-5;
lv.18 --- A-2; B-2;
lv.19 --- A-2; B-2; A-3; B-3; A-5;
lv.20 --- A-2; B-2;
lv.21 --- A-2; B-2; A-3; B-3;
lv.22 --- A-2; B-2;
lv.23 --- A-2; B-2; A-3; B-3; A-5;
lv.24 --- A-2; B-2;
lv.25 --- A-2; B-2; A-3; B-3; A-5; B-5;
lv.26 --- A-2; B-2;
lv.27 --- A-2; B-2; A-3; B-3;
lv.28 --- A-2; B-2;
lv.29 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7;
lv.30 --- A-2; B-2;
lv.31 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7;
lv.32 --- A-2; B-2;
lv.33 --- A-2; B-2; A-3; B-3;
lv.34 --- A-2; B-2;
lv.35 --- A-2; B-2; A-3; B-3; A-5; B-5;
lv.36 --- A-2; B-2;
lv.37 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7;
lv.38 --- A-2; B-2;
lv.39 --- A-2; B-2; A-3; B-3;
lv.40 --- A-2; B-2;
lv.41 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7;
lv.42 --- A-2; B-2;
lv.43 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7;
lv.44 --- A-2; B-2;
lv.45 --- A-2; B-2; A-3; B-3;
lv.46 --- A-2; B-2;
lv.47 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7;
lv.48 --- A-2; B-2;
lv.49 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7;
lv.50 --- A-2; B-2;
lv.51 --- A-2; B-2; A-3; B-3;
lv.52 --- A-2; B-2;
lv.53 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7; A-11;
lv.54 --- A-2; B-2;
lv.55 --- A-2; B-2; A-3; B-3; A-5; B-5;
lv.56 --- A-2; B-2;
lv.57 --- A-2; B-2; A-3; B-3;
lv.58 --- A-2; B-2;
lv.59 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7; A-11;
lv.60 --- A-2; B-2;
lv.61 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7; A-11;
lv.62 --- A-2; B-2;
lv.63 --- A-2; B-2; A-3; B-3;
lv.64 --- A-2; B-2;
lv.65 --- A-2; B-2; A-3; B-3; A-5; B-5;
lv.66 --- A-2; B-2;
lv.67 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7; A-11;
lv.68 --- A-2; B-2;
lv.69 --- A-2; B-2; A-3; B-3;
lv.70 --- A-2; B-2;
lv.71 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7; A-11;
lv.72 --- A-2; B-2;
lv.73 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7; A-11;
lv.74 --- A-2; B-2;
lv.75 --- A-2; B-2; A-3; B-3;
lv.76 --- A-2; B-2;
lv.77 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7;
lv.78 --- A-2; B-2;
lv.79 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7; A-11;
lv.80 --- A-2; B-2;
lv.81 --- A-2; B-2; A-3; B-3;
lv.82 --- A-2; B-2;
lv.83 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7; A-11;
lv.84 --- A-2; B-2;
lv.85 --- A-2; B-2; A-3; B-3; A-5; B-5;
lv.86 --- A-2; B-2;
lv.87 --- A-2; B-2; A-3; B-3;
lv.88 --- A-2; B-2;
lv.89 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7; A-11;
lv.90 --- A-2; B-2;
lv.91 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7;
lv.92 --- A-2; B-2;
lv.93 --- A-2; B-2; A-3; B-3;
lv.94 --- A-2; B-2;
lv.95 --- A-2; B-2; A-3; B-3; A-5; B-5;
lv.96 --- A-2; B-2;
lv.97 --- A-2; B-2; A-3; B-3; A-5; B-5; A-7; B-7; A-11;
lv.98 --- A-2; B-2;
lv.99 --- A-2; B-2; A-3; B-3;
lv.100 --- A-2; B-2;
[2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47; 53; 59; 61; 67; 71; 73; 79; 83; 89; 97]

这里A表示进入了takeWhile的条件函数,B表示进入了exists的条件函数。由于是延迟执行的,所以我们看到A和B成功被合并到一次迭代中,A、B交替执行,而不是一股脑执行完A再一股脑执行B。从而只要A或者B有一个达到了短路条件,整个过程都会短路返回。

例如:

  • 对于4,我们遍历已存在的质数列表[2; 3],遍历2时,进入takeWhile的条件,发现它不小于4的平方根,不满足短路条件;继而进入exists,发现2可以整除4,满足短路条件,于是返回false,不再遍历剩下的元素3。
  • 对于5,我们遍历已存在的质数列表[2; 3],进入A发现满足2<根号5,进入B发现2不能整除5;继续遍历3,发现3已经不满足小于根号5了,短路触发,从而B不再执行,返回true。

利用上面特性,F#也成功解决了序列处理的短路的问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

函数式,F#都做了哪些优化? 的相关文章

  • 在源代码管理中管理我的数据库

    由于我正在处理一个新的数据库项目 在 VS2008 中 而且我从未从头开始开发数据库 因此我立即开始研究如何在源代码管理 在本例中为 Subversion 中管理数据库 我找到了一些关于SO的信息 包括这篇文章 保持多个环境中的开发数据库同
  • 如何使用 Serilog ForContext

    我是 Serilog 新手 很难弄清楚如何使用上下文功能 当我运行下面的代码时 输 出文件不包含报告 ID 我缺少什么想法吗 var logger new LoggerConfiguration WriteTo File C Log txt
  • LINQ:获取表详细信息

    我正在使用 LINQPad 我想了解表的架构详细信息 我知道我是用 SQL 来做的 SELECT column name FROM information schema columns WHERE table name table name
  • 有人有 Postsharp 制作经验吗? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • C#、Visual Basic.NET 和托管 C++ 之间的字符串文字有何区别?

    我指的是在代码中编写字符串的语法 包括多行字符串和逐字字符串 上下文 我正在开发一个扫描代码的工具 确定令牌何时位于字符串内非常重要 Thanks 这是语言之间的快速细分 托管 C 以与普通 C C 或 C 类似的方式支持字符串文字 也就是
  • 编译时和运行时转换 C#

    我想知道为什么 C 中的某些强制转换会在编译时进行检查 而在其他情况下则将责任转嫁给 CLR 如上所述 两者都是不正确的 但处理方式不同 class Base class Derived Base class Other static vo
  • F# 类型提供程序与 Lisp 宏

    我一直在阅读有关 F 3 0 类型提供程序的内容 例如here http msdn microsoft com en us library hh156509 aspx 并且它们似乎基于一种编译时代码生成 在这方面我想知道它们与 Lisp 宏
  • 作为服务运行时未找到 PowerShell 模块

    我有一个项目 我将 Office 365 许可证分配给用户 有一个 Web 项目允许管理员查看可用许可证并为用户选择许可证 在后台 有一项服务每 15 分钟执行一次实际的许可证分配 以及许多其他任务 我们不直接分配许可证的原因是用户可能尚未
  • “为 .Net 1.1 生成 Web 服务代理的任何工具”

    我有一个 WSDL 文件 其中包含指向外部 XSD 的链接 我也将 xsd 部署到了正确的相对路径 因为它在 WSDL 中是引用的 当我尝试使用 VS 2003 Net 1 1 wsdl 工具 生成代理时 代理未生成 然而它在 VS 200
  • 创建仅包含枚举的 COM 库时出现问题

    我正在做一个 COM 互操作项目 用 C 和 NET Interop 替代一些 VB 和 C ATL COM 项目 当我在 NET 中定义枚举并将它们设为 ComVisible 时 它 们会公开为 Typelib EnumType Enum
  • 是否可以在对Where 的调用中调用命名方法?

    我试图从 RedGate 的这本免费电子书中了解 Linq 的一些性能影响ftp support red gate com ebooks under the hood of net memory management part1 pdf f
  • 从 git 签出后 nuget dll 丢失

    I have a C solution containing different projects On those projects I have some normal nuget packages like Newtonsoft Js
  • .NET 中的线程中止

    我有一个线程正在分析文件并对数据库进行事务调用 每个事务都有一个审计条目作为其事务的一部分 调用 Thread Abort 来停止文件的处理有什么大问题吗 而不是到处散布丑陋的安全点 文件将在 Abort 调用后关闭 明显的问题是放弃交易的
  • HttpUtility.ParseQueryString 不解码特殊字符

    Uri uri new Uri redirectionUrl NameValueCollection col HttpUtility ParseQueryString uri Query uri Query已经被解码 那么我有什么办法可以阻
  • C# - 挂钩现有 COM 对象

    假设我们有一个现有进程 或应用程序 它从 ocx 文件 例如 MyCOMLibrary ocx 调用 COM 对象 有没有办法编写一个 C 库来精确复制 ocx 文件 这样原始应用程序就可以调用您的 C 代码而不是原始 COM 对象 当然
  • MetadataException:无法加载指定的元数据资源

    突然间我不断收到MetadataException在实例化我生成的ObjectContext班级 App Config 中的连接字符串看起来正确 自上次工作以来没有更改 我尝试从底层数据库重新生成一个新模型 edmx 文件 没有任何更改 有
  • 为什么在按下 Tab 键之前​​不显示焦点矩形?

    我有一个奇怪的问题 可能只是为了理解 为什么在测试应用程序中 直到我按 Tab 键才显示焦点矩形 我想显示一个带有两个单选框和两个按钮的对话框 当我显示对话框时 我希望在第一个单选按钮周围看到一个焦点矩形 以便用户可以看到焦点在哪里 我对控
  • 导入 IronPython 的嵌入式库

    我已通过 NuGet 将 IronPython 标准库添加到我的 c net4 项目中 后来获得了一些参考资料 IronPython IronPython Modules IronPython SQLite IronPython Wpf M
  • Control.Invoke 是否泵送消息?

    Control Invoke 在被阻塞时是否继续在调用它的线程中泵送消息 系统 Windows 窗体 不会 Invoke 是阻塞的 整个线程将阻塞 直到对 Invoke 的调用返回 还有开始调用 http msdn microsoft co
  • 表单上的 KeyEvents 只能与 CTRL 结合使用

    Code Private Sub KeyHandling ByVal sender As Object ByVal e As System Windows Forms KeyEventArgs Handles Me KeyDown Sele

随机推荐

  • 图:最小生成树

    一 最小生成树 1 1 生成树的定义 一个连通图的生成树是 个极小的连通子图 它包含图中全部的n个顶点 但只有构成 棵树的n 1条边 连通图和它相对应的 成树 可以 于解决实际生活中的问题 假设A B C 和 D 为 4 座城市 为了 便
  • window服务器上发布net项目,在windows服务器上使用winsw部署spring boot项目

    简介 springboot项目需要在windows上部署 spring官方推荐使用winsw来将springboot项目作为服务运行 参考 安装使用 winsw的使用比较简单 从github上下载 winsw下载 要下载的文件有两个 1 w
  • 《Kotlin从小白到大牛》第22章:Kotlin I/O与文件管理

    第22章 Kotlin I O与文件管理 Kotlin I O 输入与输出 是基于Java I O流技术 但是Java I O流技术使用起来比较繁琐 Kotlin提供了很多扩展 使代码变得简洁 本章介绍Kotlin I O流和文件管理相关知
  • 使用jstack排查线上故障:高CPU占用

    1 前言 一个应用占用CPU很高 除了确实是计算密集型应用之外 通常原因都是出现了死循环 我们以当时出现的实际故障为例 来介绍怎么定位和解决这类问题 2 排查步骤 思路 找出tomcat 进程中使用CPU最高 时间最长的线程 分析堆栈信息
  • Java中栈的实现(1)-使用顺序存储结构(数组)以及实现

    栈和队列其实是与普通的线性发展而来的 为普通的线性表增加一些特殊的限制就可以得到栈和队列了 从功能上看 栈和队列比普通的线性表功能相对弱一点 但是在特殊的场合下 使用栈和队列更有利 例如 编译器在实现函数的调用的时候需要使用栈来存储断点 实
  • js中apply方法的使用详细解析_宿雪家的小镇_新浪博客

    1 对象的继承 一般的做法是复制 Object extendprototype js的实现方式是 复制代码代码如下 Object extend function destination source for property in sour
  • Android平台实现ping功能方案----避过ping数据构建需要root权限的限制

    PingForAndroid C库采用SOCK DGRAM方式构建icmp包 避开raw socket必须root权限的限制 实现ping功能 Github https github com bgylde PingForAndroid pi
  • linux shell 等待输入_shell中获得用户的输入

    有时我们需要shell脚本有更多的交互性 比如我们安装某个linux命令的时候 会提示 N Y 选择安装或者放弃 这时就需要用到shell的read命令 read命令的基本使用 演示代码如下 其中 n表示字符串输出尾端不换行 还可以直接在r
  • R语言 回归诊断几种方法

    回归诊断技术提供了评价回归模型使用性的必要工具 能帮助发现并且纠正问题 有几种方法进行回归诊断 分别是标准方法 car包中的函数 gvlma函数 建议先通过gvlma函数进行验证 如果违反假设条件 再使用其他方法来判断哪些假设没有满足并进行
  • Cloud Run min实例:最小化无服务器的冷启动

    作者 Kelsey Hightower Vinod Ramachandran 无服务器最为优异的一点在于其按需付费的操作模型 可以让用户将服务规模缩减为零 但是对于某些应用程序来说 无服务器对于他们来说没那么重要的原因也在于其模型可以按需缩
  • 春秋云镜 CVE-2022-24112

    春秋云镜 CVE 2022 24112 Apache APISIX batch requests SSRF RCE 漏洞 靶标介绍 Apache Apisix是美国阿帕奇 Apache 基金会的一个云原生的微服务API网关服务 该软件基于O
  • opencv 轮廓放大_使用OpenCV和Python构建自己的车辆检测模型

    概述 你对智慧城市领域有兴趣吗 如果是的话 你会喜欢这个关于实现你自己的车辆检测系统的教程 在深入实现部分之前 我们将首先了解如何检测视频中的移动目标 我们将使用OpenCV和Python构建自动车辆检测器 介绍 关于智慧城市的理念 以及自
  • 三分钟了解腾讯云DDOS基础防护

    三分钟了解腾讯云DDOS基础防护 DDoS 基础防护应用场景包括哪些 腾讯云 DDoS 基础防护应用于攻击频率不高且攻击峰值不超过基础防护阈值的 DDoS 攻击防护场景 当攻击流量超过一定流量时 将自动启动 DDoS 清洗设备进行流量清洗
  • nettty

    https www jianshu com p e58674eb4c7a 1 Netty 异步和事件驱动 1 Netty 能够帮助搭建允许系统能够扩展到支持150000名并发用户 2 Netty 设计关键 异步 事件驱动 1 1 Java网
  • Linux下JDK1.8安装及配置

    进入Linux系统Ubuntu debian或centos安装JDK1 8并配置环境变量 通过终端在 opt目录下新建base文件夹 命令行 sudo mkdir opt base 然后进入java目录 命令行 cd opt base 到官
  • Ubuntu安装Nvidia显卡驱动

    原来没那么复杂 https linuxconfig org how to install the nvidia drivers on ubuntu 18 04 bionic beaver linux 我用的第一种方法 安装途中遇到了一个 v
  • php ajax右边悬浮购物车,一个简单的php+Ajax购物车程序代码(1/2)_PHP教程

    cart name name this gt items SESSION this gt cart name setItemQuantity Set the quantity of an item param string order co
  • 远程服务器docker配置并启动jupyter lab

    前提摘要 在远程服务器docker中存在镜像 但想通过jupyter lab来更好的可视化代码结果 首先登录远程服务器ssh 并拉取镜像 p是端口映射 22给ssh连接 jupyter lab的默认端口是8889 8888是自己的端口号 n
  • Ubuntu下OpenResty 搭建高性能服务端

    Socke 介绍 Linux Socket 编程领域为了处理大量连接请求场景 需要使用非阻塞 I O 和复用 select poll epoll 是 Linux API 提供的 I O 复用方式 自从 Linux2 6 中加入了 epoll
  • 函数式,F#都做了哪些优化?

    非函数式语言中使用函数式风格的缺点 函数式的优点 想必大家都已经非常了解了 我们来看看 一般语言使用函数式风格可能带来的问题 变量默认是可变的 为了实现不可变性 开发者只能人为的规范不去改变变量的值 没有明确的变量修改提示 容易因失误改变变