刘悦的技术博客

刘悦的技术博客 查看完整档案

北京编辑北京交通大学  |  计算机科学与技术 编辑  |  填写所在公司/组织 v3u.cn 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

刘悦的技术博客 发布了文章 · 4月6日

奇技淫巧玄妙无穷| M1 mac os(苹果/AppleSilicon)系统的基本操作和设置

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_191

最近有个朋友跟我说,说他新入职了一家公司,公司还不错,给他配了一台Mac,但是呢他以前一直在Windows环境下开发,对Mac os并不了解,他感到很彷徨,所以本次呢,我们来分享一下,当手头儿有一部崭新的Mac,我们应该怎么上手操作和配置,让它成为我们开发的好帮手。

首先我们来看一下键位上的差异,传统Windows系统的键盘以control键为主,以左小指为基准,食指为辐射范围,组合快捷键,比如说我们非常熟悉control+c 和 control +v:

Mac系统的键位是以command键为主,以左拇指为基准,食指为辐射范围,组合快捷键,用习惯了windows朋友只要脑子想着原本的control键改为command键即可,这样上手呢就方便很多:

接下来,我们来看看Mac的快捷键:

乍一看,我靠,怎么这么多,其实我们一开始不用记那么多,只要记住一些常用的快捷键即可:

首先我们来打开finder,mac里的finder就相当于windows系统的此电脑,电脑中的文件都会在finder里显示,这里如果想选中某个文件,并不需要鼠标的参与,只要点击tab键,即可让光标选中文件,多点几次呢,就可以重新选择,这时候组合快捷键command + i 就可以显示该文件的具体信息,大家可以看到该文件的一些属性,所以第一个快捷键 command + i,为了方便记忆 i 可以理解为information信息,ok,怎么关闭这个信息窗口呢?可以使用command + w,无论什么窗口都可以command + w来关闭,w可以理解为wave,也就是挥手再见的意思,command + w ,关闭窗口。

接着来我们来操作一下复制文件,command + c,c就是copy的意思这和windows系统里的复制文件相差无几,command + v,粘贴文件,复制粘贴也是我们操作频率比较高的操作,那么windows系统下,经常有一种操作叫做剪切,它的快捷键是control + x ,在mac中如果想剪切文件的话,需要使用组合键command + option + v,这样才能剪切文件。

有的时候我们需要修改文件的文件名,在mac系统中,只要选中文件按回车,就可以很方便的修改文件名,同时呢,可以使用 command +和command - 来放大修改图标。

另外一个经常会用到的组合键是:command q , 我们知道使用command + w 可以关闭窗口,但是无法退出程序,如果想彻底退出程序的,使用 command + q 就可以彻底退出,q 意味着 quit,比如我打开sfari浏览器,command + q 就可以退出,而command w 只能关闭某个浏览器窗口。

除了快捷键,我们介绍一个finder里经常会用到的路径栏设置,大家可以看到我的finder文件夹下面有一个当前目录的文件路径,可以很方便的查看它的上一级目录,这个在默认设置里是不显示的,需要手动打开,那么在finder的显示菜单,选择显示路径栏即可,这个路径栏可以复制,然后在终端内就可以很方便的进入某个文件的终端,而在终端内,我也可以非常的在finder中打开,输入 open . 即可,非常方便

让我们来看看触控板的设置,点击系统偏好设置,选择触控板,在光标与点按的选项中默认轻点是没有选上的,建议大家选上,这样呢触控板只需要手指轻轻触碰,不用真正的按下去即可达到效果。

在更多手势的选项中,我个人建议把页面之间的横扫取消掉,因为在查看网页的时候呢,非常容易误触,比如打开网页,双指横移往左就后退网页,往右就是前进,假设我们正在编辑某个表单,一旦误触回退,数据就丢失了,所以我取消掉了页面横扫,用快捷键来代替,command+ 左方向键 就是后退,command+右方向键 就是前进,也挺方便的。

下面来看看Mac os系统中,超级好用的三指拖移功能,它的设置隐藏的比较深,在系统偏好设置-》辅助功能-》指针控制-》触控板选项中

三指拖移的好处就是再也不需要我们按住触控板很费劲的移动光标元素了,只需要三指轻触就可以随意拖动文件,增加了操作的灵活度与顺滑感。

很多人不太适应Mac os的搜索功能,在finder中,默认搜索其实是全域检索,也就是检索本地磁盘的所有地方,速度会比较慢,如果只想在当前文件夹下搜索,则需要再点选一下才可以:

紧接着来说说我个人非常喜欢的触发角,在系统偏好设置里选择桌面与屏保,点击屏保选择触发角,我一般情况下使用屏幕的右边的两个角,因为右面离光标会更近,可以更快的触发,而左面的触发角需要光标横向移动一段时间才可以触发,如果使用触控板操作会极其不方便。右上角选择调度中心,右下角选择桌面,什么意思呢,就是光标一旦移动右上角,就可以很方便的选择当前任务,移动右下角可以立刻切到桌面。

这样呢,如果上班摸鱼,老板突然进来,我们用触发角就可以很方便的切回桌面,但是也会导致另外一种尴尬情况,就是老板会质疑你,为什么他每次进来,你都在盯着桌面看呢?

让我们来看看开发中经常用到的终端,在m1芯片mac最新的big sur系统里,终端采用的是zsh,替代了原来的bash终端,我们可以在终端的偏好设置里,选择自己喜欢的配色以及字体大小。那么zshell真正好用的地方就是它比bash多了一些功能,比如我在终端里想快速定位某个文件,双击tab键可以进行补全,再次双击呢,我就可以直接在终端进行移动选择,非常方便。

如果您的mac系统版本比较老,还在使用bash终端,比如说mojave版本的mac,就可以利用下面这个脚本进行升级,直接将bash升级为zshell,那怎么区分当前终端的类型呢,就是在终端上面,标题显示zsh就是zshell,而显示bash就是bash终端,如果显示的zsh那么意味着不需要升级,如果升级呢,就把这个脚本复制到终端回车运行即可:

sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

最后呢,谈一谈mac系统输入法的问题,这也是很多朋友们头疼的地方,在系统偏好设置,语言和地区里面,选择键盘偏好设置,这里你会发现英语是删除不了的,减号是灰色的,而搜狗输入法它是自带英语的,使用左侧shift键就可以切换,而mac系统它有时候会帮你切到系统默认的英语输入法,所以你还得用 command+空格 切回搜狗,这样就很别扭,那能不能把默认的英语删掉呢,答案是可以的。这里我们借助第三方的配置修改工具,Plist edit Pro,这里是安装包:

https://pan.baidu.com/s/1DMPXh7Oed6lftooz54BgTQ 密码: nj06

安装成功后呢,下面打开终端,输入命令:

sudo open ~/Library/Preferences/com.apple.HIToolbox.plist

系统会使用Plist edit Pro直接打开该文件

此时删掉AppleEnabledInputSources节点下的带有ABC字样的子节点即可,随后保存文件,重启电脑就可以了。重启之后呢,就可以发现输入法就只剩下中文搜狗输入法了,以后切换中英文,就可以直接用左侧shift键,比较方便,但是其实删除系统输入法也会带来一些bug,笔者发现M1 芯片的Mac如果想通过有线指定ip地址的方式连接ipv4网络,则必须用系统默认的英文输入法输入才可以,所以如果想通过系统设置手动指定ip,还得把英文输入法再添加回来,好在操作并不复杂。

结语:M1 mac 系统中一些基本的设置大概就这些了,要知道mac os博大渊深,要想成为高手,还得靠自己的深入研究,这里也不过是一块儿引玉之砖,视频攻略请移步:

https://www.bilibili.com/vide...

原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_191

查看原文

赞 0 收藏 0 评论 0

刘悦的技术博客 收藏了文章 · 3月22日

macos 上的 zsh 配置

前言

这几天升级了下 macos Catalina,打开 vscode 终端时发现提示了

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.

简而言之,就是从这个版本开始,macos 开始使用 zsh 替代 bash 用做默认的 shell 工具。那么下面来看下具体的配置方式。

下方说的应用配置,均指:source ~/.zshrc

安装

macos 默认已经安装了 zsh,贴一下安装/升级方式。

# 查看 zsh 版本
zsh --version

# 升级 zsh
brew install zsh zsh-completions

注:brew 默认是走 Github 源的,会比较慢,可以通过切换源来提速,参见[brew 提速]()

如果需要切换 macos shell 工具,可以看下苹果官网教程 - 在 Mac 上将 zsh 用作默认 Shell

使用 oh my zsh

oh my zsh 下简称 omz。

安装

安装 oh my zsh 可以通过 curl 方式或 wget 方式。

# curl
$ sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

# wget
$ sh -c "$(wget https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)"

安装后会自动生效,若未生效可重启终端,附omz 官网地址

配置主题

找到 Theme 配置代码块,可以看到

ZSH_THEME="ys"

笔者使用的是 ys,感觉挺好用的,有兴趣可以尝试下其它的主题效果。

全部主题可以在omz Github 主题列表上查看,如果示例图片速度加载的慢,可以看下这篇文章omz 主题列表

配置插件

打开 .zshrc 文件,找到 plugins 配置代码块,可以看到默认已经有了 git 配置,那么接下来再增加几个常用插件。

autojump

# 安装方式
brew install autojump

# 执行以下命令
echo '[[ -s ~/.autojump/etc/profile.d/autojump.sh ]] && . ~/.autojump/etc/profile.d/autojump.sh' >> ~/.zshrc

zsh-autosuggestions

# 安装方式
git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions

打开 ~/.zshrc 配置文件,在 plugins 配置代码块增加 zsh-autosuggestions。

使用了下插件后,发现默认的提示信息文本颜色很淡,不清晰,虽然跟我的终端底色半透黑有关系,但还是调整一下。

# 修改 zsh-autosuggestions 提示信息颜色
echo 'ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=30'' >>  ~/.zshrc
fg 值即为终端中展示的提示信息颜色,笔者目前使用的是30,选择其它颜色可参见xterm色值对照表

贴一下插件代码块示例

plugins=(
  autojump
  git
  zsh-autosuggestions
)

保存并应用配置。

保留 bash 脚本

由于之前使用的是 bash,有些个性化的命令,但是使用 zsh 后自然就失效了,那么只需要增加一点配置,即可恢复使用。方法是在 .zshrc 中找到 # User configuration 配置代码块,加入

echo "source ~/.bash_profile" >> ~/.zshrc

保存并应用配置。

vscode 切换 zsh

最后改下 vscode 配置,增加

"terminal.integrated.shell.osx": "zsh",

即可将 zsh 用做默认的 shell 工具,再打开终端就不会有提示了。

查看原文

刘悦的技术博客 发布了文章 · 3月10日

石火电光追风逐日|前端优化之次时代图片压缩格式WebP的项目级躬身实践(Python3 PIL+Nginx)

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_190

我们知道,在前端界有一个共识:速度就是生命,带宽就是金钱。怎样将页面加载速度有效提升是无数前端工程师无时不刻在思考的课题,目前的网络环境中,除了视频,图片仍旧是占用流量较大的一部分,对于app端尤其如此,因此,如何在保证图片视觉不失真的前提下缩小图片体积,对于节省带宽和电池电量都十分重要,因此Google在十年前提出了一种新的图片压缩格式 :WebP,给图片的优化提供了新的方向。

WebP的优势在于它具有更优的图像数据压缩算法,在拥有肉眼几乎无法识别差异的图像质量前提下,带来更小的图片体积,同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,从JPEG 和 PNG 上的转化效果都非常突出、稳定和统一,全球著名视频网站YouTube的视频缩略图采用WebP后,网页加载速度提升了10%,提升效果可见一斑:

本次我们以本站为例子,使用Python3对站内图片进行无损压缩和转换,同时利用Nginx针对Webp图片判断请求头来对老版本浏览器做向下兼容,实现Webp图片的无缝切换。

首先,将网站的图片转换为Webp格式,这里我们使用PIL库,该库广泛用于Python中的图像处理,并且PIL图像库中最重要的类是Image类,该类在模块中以相同的名称定义。

安装PIL:

python3 -m pip install --upgrade pip  
python3 -m pip install --upgrade Pillow

可以通过open方法加载图像文件并且展示它:

from PIL import Image  
img = Image.open('sample.jpg')  
img.show()

Image.convert()方法可以返回该图像的转换后的副本。此方法可通过调色板转换像素。当前版本支持“ L”,“ RGB”和“ CMYK”之间的所有格式转换。save(fp,format)使用两个输入参数,第一个是保存转换后的文件的文件路径(fp),第二个是要转换成的文件格式。

转换JPG到PNG:

from PIL import Image  
img = Image.open('sample.jpg').convert('RGB')  
img.save('sample.jpg.png', 'png')

转换PNG到JPG:

from PIL import Image  
img = Image.open('sample.png').convert('RGB')  
img.save('sample.png.jpeg', 'jpeg')

转换PNG到WEBP:

from PIL import Image  
img = Image.open('sample.png').convert('RGB')  
img.save('sample.png.webp', 'webp')

转换JPG到WEBP:

from PIL import Image  
img = Image.open('sample.jpg').convert('RGB')  
img.save('sample.jpg.webp', 'webp')

看起来相当简单,有的时候,我们可能需要把某个目录下的图片进行批量转换,首先读取所有需要转换的图片:

import os  
  
files = os.listdir('/opt/img')  
  
images = [file for file in files if file.endswith(('jpg','png','jpeg'))]  
  
print(images)

liuyue:mytornado liuyue$ python3 "/Users/liuyue/wodfan/work/mytornado/test_webp.py"  
['qrcode.png', 'touxiang_1.png', 'code.jpeg', 'test.png', 'touxiang1.jpg', 'logo_dark.png', 'logo.png', 'touxiang.png', 'code1.jpeg']  
liuyue:mytornado liuyue$ 

随后建立转换方法:

def convert_image(image_path, image_type):  
  
    im = Image.open(image_path)  
    print(image_path)  
    im = im.convert('RGB')  
    image_name = image_path.split('.')[0]  
    print(f"This is the image name: {image_name}")  
  
    if not os.path.exists(f"{image_path}.webp"):  
  
        if image_type == 'jpg' or image_type == 'png' or image_type == 'jpeg':  
            im.save(f"{image_name}.{image_type}.webp", 'webp')  
        else:  
            raise Error

这里我们将转换后的副本统一加上后缀.webp

之后进行转换操作:

for image in images:  
    if image.endswith('jpg'):  
        convert_image(image, image_type='jpg')  
    elif image.endswith('jpeg'):  
        convert_image(image, image_type='jpg')  
    elif image.endswith('png'):  
        convert_image(image, image_type='png')  
    else:  
        raise Error

这里需要注意的是,是在原图文件名基础上加入后缀.webp,而不是覆盖原图后缀,这样后面替换图片内容时会更加方便。

接下来的课题就是怎样判断客户端的浏览器是否支持Webp格式的图片,目前ios和新版的Safari浏览器已经对webp进行了适配,但是对于老版本的系统和浏览器怎么向下兼容是一个令人头疼的问题。

可以在前端通过js脚本来进行判断:

window.isSupportWebp = false;//是否支持  
(function() {  
    var img = new Image();   
    function getResult(event) {  
        //如果进入加载且图片宽度为1(通过图片宽度值判断图片是否可以显示)  
        window.isSupportWebp = event && event.type === 'load' ? img.width == 1 : false;  
    }  
    img.onerror = getResult;  
    img.onload = getResult;  
    img.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA='; //一像素图片  
})();  
  
console.log(window.isSupportWebp);  
true

原理就是加载一像素的webp判断是否显示成功,如果window.isSupportWebp为true我们就可以将webp后缀加载否则就加载原后缀的图片,但是基于前端的解决方案需要修改大量的代码,同时如果判断业务逻辑放在页面里无形中也增加了页面负担,有没有方法在不变动代码逻辑的前提下,可以自动切换图片后缀呢?答案就在后端的Nginx。

我们知道浏览器的每个请求头中都带有"Accept"字段,例如:

Accept:image/webp,image/apng,image/*,*/*;q=0.8

此时通过nginx对Accept进行判断,如果带有webp,说明该浏览器支持webp,我们就由后端加载webp,如果头部没有webp字样,说明浏览器不支持,此时nginx继续加载原后缀文件,这也就是为什么之前在图片转换过程中要保留原始图片文件的原因。

首先打开nginx的mime.types文件,查看nginx是否配置webp,如果没有需要手动加上:

vim /etc/nginx/mime.types

可以看到全部文件类型:

types {  
    text/html                                        html htm shtml;  
    text/css                                         css;  
    text/xml                                         xml;  
    image/gif                                        gif;  
    image/jpeg                                       jpeg jpg;  
    application/javascript                           js;  
    application/atom+xml                             atom;  
    application/rss+xml                              rss;  
  
    text/mathml                                      mml;  
    text/plain                                       txt;  
    text/vnd.sun.j2me.app-descriptor                 jad;  
    text/vnd.wap.wml                                 wml;  
    text/x-component                                 htc;  
  
    image/png                                        png;  
    image/svg+xml                                    svg svgz;  
    image/tiff                                       tif tiff;  
    image/vnd.wap.wbmp                               wbmp;  
    image/webp                                       webp;  
    image/x-icon                                     ico;  
    image/x-jng                                      jng;  
"/etc/nginx/mime.types" 97L, 5231C                            22,5         顶端  
    application/octet-stream                         deb;  
    application/octet-stream                         dmg;  
    application/octet-stream                         iso img;  
    application/octet-stream                         msi msp msm;  
  
    audio/midi                                       mid midi kar;  
    audio/mpeg                                       mp3;  
    audio/ogg                                        ogg;  
    audio/x-m4a                                      m4a;  
    audio/x-realaudio                                ra;  
  
    video/3gpp                                       3gpp 3gp;  
    video/mp2t                                       ts;  
    video/mp4                                        mp4;  
    video/mpeg                                       mpeg mpg;  
    video/quicktime                                  mov;  
    video/webm                                       webm;  
    video/x-flv                                      flv;  
    video/x-m4v                                      m4v;  
    video/x-mng                                      mng;  
    video/x-ms-asf                                   asx asf;  
    video/x-ms-wmv                                   wmv;  
    video/x-msvideo                                  avi;  
}

主要查看有没有webp,如果没有进行添加:

image/webp                                       webp;

随后修改主配置文件:

vim /etc/nginx/nginx.conf

在http配置中加上webp文件的判断逻辑:

map $http_accept $webp_suffix {  
    default   "";  
    "~*webp"  ".webp";  
}

同时在server中配置逻辑,如果支持就将头部信息替换为webp后缀文件:

location ~* ^/v3u/Public/images/.+\.(png|jpe?g)$ {  
  add_header Vary Accept;  
  try_files $uri$webp_suffix $uri =404;  
}

完整配置如下:

user root;  
worker_processes auto;  
error_log /var/log/nginx/error.log;  
pid /run/nginx.pid;  
  
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.  
include /usr/share/nginx/modules/*.conf;  
  
events {  
    worker_connections 1024;  
}  
  
http {  
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '  
                      '$status $body_bytes_sent "$http_referer" '  
                      '"$http_user_agent" "$http_x_forwarded_for"';  
  
    access_log  /var/log/nginx/access.log  main;  
  
    map $http_accept $webp_suffix {  
    default   "";  
    "~*webp"  ".webp";  
}  
  
    sendfile            on;  
    tcp_nopush          on;  
    tcp_nodelay         on;  
    keepalive_timeout   65;  
    types_hash_max_size 2048;  
  
    include             /etc/nginx/mime.types;  
    default_type        application/octet-stream;  
  
        gzip on;  
        gzip_min_length  1k;  
        gzip_buffers     4 16k;  
        gzip_http_version 1.0;  
        gzip_comp_level 2;  
        gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;  
        gzip_vary on;  
        gzip_disable msie6;  
  
    open_file_cache max=1000 inactive=20s;  
    open_file_cache_valid 30s;  
    open_file_cache_min_uses 5;  
    open_file_cache_errors off;  
  
    # Load modular configuration files from the /etc/nginx/conf.d directory.  
    # See http://nginx.org/en/docs/ngx_core_module.html#include  
    # for more information.  
    include /etc/nginx/conf.d/*.conf;  
  
    server {  
          
        location ~* ^/v3u/Public/images/.+\.(png|jpe?g)$ {  
  add_header Vary Accept;  
  try_files $uri$webp_suffix $uri =404;  
}  
  
        include /etc/nginx/default.d/*.conf;  
  
        location / {  
        }  
  
  
        error_page 404 /404.html;  
            location = /40x.html {  
        }  
  
        error_page 500 502 503 504 /50x.html;  
            location = /50x.html {  
        }  
    }  
  
#include vhost/*.conf;  
}

修改好配置文件之后,先不要着急重启服务器,检测一下配置文件语法:

[root@iz2ze0ndt5s9wq2s6ff8g6z nginx]# nginx -t  
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok  
nginx: configuration file /etc/nginx/nginx.conf test is successful  
[root@iz2ze0ndt5s9wq2s6ff8g6z nginx]#

如果没有问题,重启服务:

systemctl restart nginx.service

现在让我们来测试一下,以本站的logo图片为例子,如果是不支持webp格式的浏览器,比如低版本的Safari(13.0.3):

可以看到加载图片的类型保持了原图片后缀:png

现在换一个浏览器,使用支持webp的chrome(88.0.4324):

可以看到已经自动切换为webp格式了,让我们再次通过google的页面性能打分工具PageSpeedInsights对本站进行评测: https://developers.google.com...

一望而知,页面加载速度得到了提升,并且在Google的页面优化建议中,已经达成了采用新一代格式提供图片的要求。

结语:当然了,其实Nginx是可以对图片进行实时压缩的,但是那样需要单独安装模块以及其他服务的介入,这对于低版本服务器无疑是要耗费成本的,所以本方案就是通过python3脚本提前将图片转换好,再用nginx判断一下即可,仅仅一个脚本+两行配置文件即可完成系统升级,看起来此方案是最佳选择,既没有前端代码侵入,也不需要各种复杂的配置和服务搭建。相信在不远的将来,基于google开源的VP8视频编码格式的 WebM 视频也将会大面积的替代传统的mp4格式,前端架构的性能优化,始终是业界亘古不变的课题之一。

原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_190

查看原文

赞 0 收藏 0 评论 0

刘悦的技术博客 发布了文章 · 3月4日

金玉良缘易配而木石前盟难得|M1 Mac 配置Python3开发环境搭建(集成Tensorflow/Pytorch)

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_189

笔者投入M1的怀抱已经有一段时间了,俗话说得好,但闻新人笑,不见旧人哭,Intel mac早已被束之高阁,而M1 mac已经不能用真香来形容了,简直就是“香透满堂金玉彩,扇遮半面桃花开!”,轻抚M1 mac那滑若柔荑的秒控键盘,别说996了,就是007,我们也能安之若素,也可以笑慰平生。好了,日常吹M1的环节结束,正所谓剑虽利,不厉不断,材虽美,不学不高。本次我们尝试在M1 Mac os 中搭建Python3的开发环境。

一般情况下,直接Python官网(python.org)下载最新的基于arm架构的python3.9即可,但是由于向下兼容等问题,我们尝试使用Python多版本管理软件conda,conda在业界有三大巨头分别是:Anaconda、Miniconda以及Condaforge,虽然都放出消息要适配M1芯片,但是目前最先放出稳定版的是Condaforge,进入下载页面:https://github.com/conda-forg... 选择mac arm64位架构:

该文件并不是安装包,而是一个shell脚本,下载成功后,进入命令行目录:

cd ~/Downloas

执行命令进行安装:

sudo bash ./Miniforge3-MacOSX-arm64.sh

随后会有一些条款需要确认,这里按回车之后键入yes:

Welcome to Miniforge3 4.9.2-7  
  
In order to continue the installation process, please review the license  
agreement.  
Please, press ENTER to continue  
>>>   
BSD 3-clause license  
Copyright (c) 2019-2020, conda-forge  
All rights reserved.  
  
Redistribution and use in source and binary forms, with or without  
modification, are permitted provided that the following conditions are met:  
  
1. Redistributions of source code must retain the above copyright notice, this  
list of conditions and the following disclaimer.  
  
2. Redistributions in binary form must reproduce the above copyright notice,  
this list of conditions and the following disclaimer in the documentation  
and/or other materials provided with the distribution.  
  
3. Neither the name of the copyright holder nor the names of its contributors  
may be used to endorse or promote products derived from this software without  
specific prior written permission.  
  
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND  
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED  
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE  
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE  
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL  
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR  
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER  
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,  
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
  
  
Do you accept the license terms? [yes|no]  
[no] >>> yes

安装的默认版本还是3.9,会附带安装35个基础库,这样就不用我们自己手动安装了:

brotlipy                    0.7.0  py39h46acfd9_1001   installed           
  bzip2                       1.0.8  h27ca646_4          installed           
  ca-certificates         2020.12.5  h4653dfc_0          installed           
  certifi                 2020.12.5  py39h2804cbe_1      installed           
  cffi                       1.14.5  py39h702c04f_0      installed           
  chardet                     4.0.0  py39h2804cbe_1      installed           
  conda                       4.9.2  py39h2804cbe_0      installed           
  conda-package-handling      1.7.2  py39h51e6412_0      installed           
  cryptography                3.4.4  py39h6e07874_0      installed           
  idna                         2.10  pyh9f0ad1d_0        installed           
  libcxx                     11.0.1  h168391b_0          installed           
  libffi                        3.3  h9f76cd9_2          installed           
  ncurses                       6.2  h9aa5885_4          installed           
  openssl                    1.1.1j  h27ca646_0          installed           
  pip                        21.0.1  pyhd8ed1ab_0        installed           
  pycosat                     0.6.3  py39h46acfd9_1006   installed           
  pycparser                    2.20  pyh9f0ad1d_2        installed           
  pyopenssl                  20.0.1  pyhd8ed1ab_0        installed           
  pysocks                     1.7.1  py39h2804cbe_3      installed           
  python                      3.9.2  hcbd9b3a_0_cpython  installed           
  python_abi                    3.9  1_cp39              installed           
  readline                      8.0  hc8eb9b7_2          installed           
  requests                   2.25.1  pyhd3deb0d_0        installed           
  ruamel_yaml               0.15.80  py39h46acfd9_1004   installed           
  setuptools                 49.6.0  py39h2804cbe_3      installed           
  six                        1.15.0  pyh9f0ad1d_0        installed           
  sqlite                     3.34.0  h6d56c25_0          installed           
  tk                         8.6.10  hf7e6567_1          installed           
  tqdm                       4.57.0  pyhd8ed1ab_0        installed           
  tzdata                      2021a  he74cb21_0          installed           
  urllib3                    1.26.3  pyhd8ed1ab_0        installed           
  wheel                      0.36.2  pyhd3deb0d_0        installed           
  xz                          5.2.5  h642e427_1          installed           
  yaml                        0.2.5  h642e427_0          installed           
  zlib                       1.2.11  h31e879b_1009       installed

然后编辑配置文件vim ~/.zshrc,加入如下内容(此处liuyue是笔者用户名,需改成你的Mac当前用户名):

path=('/Users/liuyue/miniforge3/bin' $path)  
export PATH

存盘之后执行命令:

source ~/.zshrc

配置好环境变量之后,键入python3:

➜  ~ python3  
Python 3.9.2 | packaged by conda-forge | (default, Feb 21 2021, 05:00:30)   
[Clang 11.0.1 ] on darwin  
Type "help", "copyright", "credits" or "license" for more information.  
>>>

可以看到已经使用conda安装的python版本了。

这里简单介绍一下conda命令:

conda info 可以查看当前conda的基本信息内核,平台,下载源以及目录位置:

➜  ~ conda info  
  
     active environment : None  
       user config file : /Users/liuyue/.condarc  
 populated config files : /Users/liuyue/miniforge3/.condarc  
          conda version : 4.9.2  
    conda-build version : not installed  
         python version : 3.9.2.final.0  
       virtual packages : __osx=11.2.2=0  
                          __unix=0=0  
                          __archspec=1=arm64  
       base environment : /Users/liuyue/miniforge3  (read only)  
           channel URLs : https://conda.anaconda.org/conda-forge/osx-arm64  
                          https://conda.anaconda.org/conda-forge/noarch  
          package cache : /Users/liuyue/miniforge3/pkgs  
                          /Users/liuyue/.conda/pkgs  
       envs directories : /Users/liuyue/.conda/envs  
                          /Users/liuyue/miniforge3/envs  
               platform : osx-arm64  
             user-agent : conda/4.9.2 requests/2.25.1 CPython/3.9.2 Darwin/20.3.0 OSX/11.2.2  
                UID:GID : 502:20  
             netrc file : None  
           offline mode : False

由于一些众所周知的学术问题,我们需要配置一下国内下载源:

conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/main/  
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/free/  
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge/  
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/msys2/  
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/bioconda/  
conda config --add channels https://mirrors.ustc.edu.cn/anaconda/cloud/menpo/  
  
conda config --set show_channel_urls yes

随后查看当前下载源:

conda config --show

可以看到国内源已经被添加进去了:

channel_priority: flexible  
channels:  
  - https://mirrors.ustc.edu.cn/anaconda/cloud/menpo/  
  - https://mirrors.ustc.edu.cn/anaconda/cloud/bioconda/  
  - https://mirrors.ustc.edu.cn/anaconda/cloud/msys2/  
  - https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge/  
  - https://mirrors.ustc.edu.cn/anaconda/pkgs/free/  
  - https://mirrors.ustc.edu.cn/anaconda/pkgs/main/  
  - defaults  
  - conda-forge  
client_ssl_cert: None

其他的一些conda常用命令:

1. conda --version #查看conda版本,验证是否安装

2. conda update conda #更新至最新版本,也会更新其它相关包

3. conda update --all #更新所有包

4. conda update package\_name #更新指定的包

5. conda create -n env\_name package\_name #创建名为env\_name的新环境,并在该环境下安装名为package\_name 的包,可以指定新环境的版本号,例如:conda create -n python2 python=python2.7 numpy pandas,创建了python2环境,python版本为2.7,同时还安装了numpy pandas包

6. source activate env\_name #切换至env\_name环境

7. source deactivate #退出环境

8. conda info -e #显示所有已经创建的环境

9. conda create --name new\_env\_name --clone old\_env\_name #复制old\_env\_name为new\_env\_name

10. conda remove --name env\_name –all #删除环境

11. conda list #查看所有已经安装的包

12. conda install package\_name #在当前环境中安装包

13. conda install --name env\_name package\_name #在指定环境中安装包

14. conda remove -- name env\_name package #删除指定环境中的包

15. conda remove package #删除当前环境中的包

16. conda create -n tensorflow\_env tensorflow

conda activate tensorflow\_env #conda 安装tensorflow的CPU版本

17. conda create -n tensorflow\_gpuenv tensorflow-gpu

conda activate tensorflow\_gpuenv #conda安装tensorflow的GPU版本

18. conda env remove -n env\_name #采用第10条的方法删除环境失败时,可采用这种方法

接着我们来尝试集成深度学习框架Tensorflow,由于目前默认是3.9,我们使用conda创建一个3.8的虚拟开发环境:

sudo conda create -n py38 python=3.8

安装成功后,输入命令:

conda info -e

就可以查看当前conda安装的所有版本:

➜  ~ conda info -e  
# conda environments:  
#  
base                  *  /Users/liuyue/miniforge3  
py38                     /Users/liuyue/miniforge3/envs/py38

可以看到一个默认的3.9环境,和新装的3.8环境,星号代表当前所处的环境,这里我们切换到3.8:

conda activate py38

此时环境已经切换到3.8:

(py38) ➜  ~ conda activate py38   
(py38) ➜  ~ conda info -e  
# conda environments:  
#  
base                     /Users/liuyue/miniforge3  
py38                  *  /Users/liuyue/miniforge3/envs/py38  
  
(py38) ➜  ~

下面开启深度学习框架Tensorflow之旅,由于苹果对m1芯片单独做了适配,所以不能用以前的pip方式直接进行安装,需要单独下载文件:https://github.91chifun.worke...://github.com/apple/tensorflow\_macos/releases/download/v0.1alpha1/tensorflow\_macos-0.1alpha1.tar.gz

解压文件:

tar -zxvf tensorflow_macos-0.1alpha1.tar.gz

解压后进入目录(一定要进入arm64的文件内):

cd tensorflow_macos/arm64

执行命令利用下载的arm64内核安装包进行安装:

pip install --force pip==20.2.4 wheel setuptools cached-property six  
  
pip install --upgrade --no-dependencies --force numpy-1.18.5-cp38-cp38-macosx_11_0_arm64.whl grpcio-1.33.2-cp38-cp38-macosx_11_0_arm64.whl h5py-2.10.0-cp38-cp38-macosx_11_0_arm64.whl tensorflow_addons-0.11.2+mlcompute-cp38-cp38-macosx_11_0_arm64.whl  
  
pip install absl-py astunparse flatbuffers gast google_pasta keras_preprocessing opt_einsum protobuf tensorflow_estimator termcolor typing_extensions wrapt wheel tensorboard typeguard  
  
pip install --upgrade --force --no-dependencies tensorflow_macos-0.1a1-cp38-cp38-macosx_11_0_arm64.whl

安装成功后,测试一下:

(py38) ➜  arm64 python  
Python 3.8.8 | packaged by conda-forge | (default, Feb 20 2021, 15:50:57)   
[Clang 11.0.1 ] on darwin  
Type "help", "copyright", "credits" or "license" for more information.  
>>> import tensorflow  
>>>

没有任何问题。

下面我们来测试一下M1通过Tensorflow训练模型的效率,还记得衣香鬓影的“机械姬”吗:人工智能不过尔尔,基于Python3深度学习库Keras/TensorFlow打造属于自己的聊天机器人(ChatRobot)

编写my\_chat.py:

import nltk  
import ssl  
from nltk.stem.lancaster import LancasterStemmer  
stemmer = LancasterStemmer()  
  
import numpy as np  
from tensorflow.keras.models import Sequential  
from tensorflow.keras.layers import Dense, Activation, Dropout  
from tensorflow.keras.optimizers import SGD  
import pandas as pd  
import pickle  
import random  
  
  
words = []  
classes = []  
documents = []  
ignore_words = ['?']  
# loop through each sentence in our intents patterns  
  
  
intents = {"intents": [  
        {"tag": "打招呼",  
         "patterns": ["你好", "您好", "请问", "有人吗", "师傅","不好意思","美女","帅哥","靓妹"],  
         "responses": ["您好", "又是您啊", "吃了么您内","您有事吗"],  
         "context": [""]  
        },  
        {"tag": "告别",  
         "patterns": ["再见", "拜拜", "88", "回见", "回头见"],  
         "responses": ["再见", "一路顺风", "下次见", "拜拜了您内"],  
         "context": [""]  
        },  
   ]  
}  
  
for intent in intents['intents']:  
    for pattern in intent['patterns']:  
        # tokenize each word in the sentence  
        w = nltk.word_tokenize(pattern)  
        # add to our words list  
        words.extend(w)  
        # add to documents in our corpus  
        documents.append((w, intent['tag']))  
        # add to our classes list  
        if intent['tag'] not in classes:  
            classes.append(intent['tag'])  
# stem and lower each word and remove duplicates  
words = [stemmer.stem(w.lower()) for w in words if w not in ignore_words]  
words = sorted(list(set(words)))  
# sort classes  
classes = sorted(list(set(classes)))  
# documents = combination between patterns and intents  
# print (len(documents), "documents")  
# # classes = intents  
# print (len(classes), "语境", classes)  
# # words = all words, vocabulary  
# print (len(words), "词数", words)  
  
  
# create our training data  
training = []  
# create an empty array for our output  
output_empty = [0] * len(classes)  
# training set, bag of words for each sentence  
for doc in documents:  
    # initialize our bag of words  
    bag = []  
  
    pattern_words = doc[0]  
     
    pattern_words = [stemmer.stem(word.lower()) for word in pattern_words]  
  
    for w in words:  
        bag.append(1) if w in pattern_words else bag.append(0)  
      
   
    output_row = list(output_empty)  
    output_row[classes.index(doc[1])] = 1  
      
    training.append([bag, output_row])  
  
random.shuffle(training)  
training = np.array(training)  
  
train_x = list(training[:,0])  
train_y = list(training[:,1])  
  
  
model = Sequential()  
model.add(Dense(128, input_shape=(len(train_x[0]),), activation='relu'))  
model.add(Dropout(0.5))  
model.add(Dense(64, activation='relu'))  
model.add(Dropout(0.5))  
model.add(Dense(len(train_y[0]), activation='softmax'))  
  
  
sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)  
model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])  
  
  
model.fit(np.array(train_x), np.array(train_y), epochs=200, batch_size=5, verbose=1)  
  
  
  
def clean_up_sentence(sentence):  
    # tokenize the pattern - split words into array  
    sentence_words = nltk.word_tokenize(sentence)  
    # stem each word - create short form for word  
    sentence_words = [stemmer.stem(word.lower()) for word in sentence_words]  
    return sentence_words  
# return bag of words array: 0 or 1 for each word in the bag that exists in the sentence  
def bow(sentence, words, show_details=True):  
    # tokenize the pattern  
    sentence_words = clean_up_sentence(sentence)  
    # bag of words - matrix of N words, vocabulary matrix  
    bag = [0]*len(words)    
    for s in sentence_words:  
        for i,w in enumerate(words):  
            if w == s:   
                # assign 1 if current word is in the vocabulary position  
                bag[i] = 1  
                if show_details:  
                    print ("found in bag: %s" % w)  
    return(np.array(bag))  
  
  
def classify_local(sentence):  
    ERROR_THRESHOLD = 0.25  
      
    # generate probabilities from the model  
    input_data = pd.DataFrame([bow(sentence, words)], dtype=float, index=['input'])  
    results = model.predict([input_data])[0]  
    # filter out predictions below a threshold, and provide intent index  
    results = [[i,r] for i,r in enumerate(results) if r>ERROR_THRESHOLD]  
    # sort by strength of probability  
    results.sort(key=lambda x: x[1], reverse=True)  
    return_list = []  
    for r in results:  
        return_list.append((classes[r[0]], str(r[1])))  
    # return tuple of intent and probability  
      
    return return_list  
  
  
p = bow("你好", words)  
print (p)  
  
print(classify_local('请问'))

返回结果:

(py38) ➜  mytornado git:(master) ✗ python3 test_mychat.py  
2021-03-03 22:43:21.059383: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:116] None of the MLIR optimization passes are enabled (registered 2)  
2021-03-03 22:43:21.059529: W tensorflow/core/platform/profile_utils/cpu_utils.cc:126] Failed to get CPU frequency: 0 Hz  
Epoch 1/200  
3/3 [==============================] - 0s 570us/step - loss: 0.6966 - accuracy: 0.5750  
Epoch 2/200  
3/3 [==============================] - 0s 482us/step - loss: 0.6913 - accuracy: 0.4857  
Epoch 3/200  
3/3 [==============================] - 0s 454us/step - loss: 0.6795 - accuracy: 0.4750  
Epoch 4/200  
3/3 [==============================] - 0s 434us/step - loss: 0.6913 - accuracy: 0.4750  
Epoch 5/200  
3/3 [==============================] - 0s 417us/step - loss: 0.6563 - accuracy: 0.5107  
Epoch 6/200  
3/3 [==============================] - 0s 454us/step - loss: 0.6775 - accuracy: 0.5714  
Epoch 7/200  
3/3 [==============================] - 0s 418us/step - loss: 0.6582 - accuracy: 0.6964  
Epoch 8/200  
3/3 [==============================] - 0s 487us/step - loss: 0.6418 - accuracy: 0.8071  
Epoch 9/200  
3/3 [==============================] - 0s 504us/step - loss: 0.6055 - accuracy: 0.6964  
Epoch 10/200  
3/3 [==============================] - 0s 457us/step - loss: 0.5933 - accuracy: 0.6964  
Epoch 11/200  
3/3 [==============================] - 0s 392us/step - loss: 0.6679 - accuracy: 0.5714  
Epoch 12/200  
3/3 [==============================] - 0s 427us/step - loss: 0.6060 - accuracy: 0.7464  
Epoch 13/200  
3/3 [==============================] - 0s 425us/step - loss: 0.6677 - accuracy: 0.5964  
Epoch 14/200  
3/3 [==============================] - 0s 420us/step - loss: 0.6208 - accuracy: 0.6214  
Epoch 15/200  
3/3 [==============================] - 0s 401us/step - loss: 0.6315 - accuracy: 0.6714  
Epoch 16/200  
3/3 [==============================] - 0s 401us/step - loss: 0.6718 - accuracy: 0.6464  
Epoch 17/200  
3/3 [==============================] - 0s 386us/step - loss: 0.6407 - accuracy: 0.6714  
Epoch 18/200  
3/3 [==============================] - 0s 505us/step - loss: 0.6031 - accuracy: 0.6464  
Epoch 19/200  
3/3 [==============================] - 0s 407us/step - loss: 0.6245 - accuracy: 0.6214  
Epoch 20/200  
3/3 [==============================] - 0s 422us/step - loss: 0.5805 - accuracy: 0.6964  
Epoch 21/200  
3/3 [==============================] - 0s 379us/step - loss: 0.6923 - accuracy: 0.5464  
Epoch 22/200  
3/3 [==============================] - 0s 396us/step - loss: 0.6383 - accuracy: 0.5714  
Epoch 23/200  
3/3 [==============================] - 0s 427us/step - loss: 0.6628 - accuracy: 0.5714  
Epoch 24/200  
3/3 [==============================] - 0s 579us/step - loss: 0.6361 - accuracy: 0.5964  
Epoch 25/200  
3/3 [==============================] - 0s 378us/step - loss: 0.5632 - accuracy: 0.7214  
Epoch 26/200  
3/3 [==============================] - 0s 387us/step - loss: 0.6851 - accuracy: 0.5214  
Epoch 27/200  
3/3 [==============================] - 0s 393us/step - loss: 0.6012 - accuracy: 0.6214  
Epoch 28/200  
3/3 [==============================] - 0s 392us/step - loss: 0.6470 - accuracy: 0.5964  
Epoch 29/200  
3/3 [==============================] - 0s 348us/step - loss: 0.6346 - accuracy: 0.6214  
Epoch 30/200  
3/3 [==============================] - 0s 362us/step - loss: 0.6350 - accuracy: 0.4964  
Epoch 31/200  
3/3 [==============================] - 0s 369us/step - loss: 0.5842 - accuracy: 0.5964  
Epoch 32/200  
3/3 [==============================] - 0s 481us/step - loss: 0.5279 - accuracy: 0.7214  
Epoch 33/200  
3/3 [==============================] - 0s 439us/step - loss: 0.5956 - accuracy: 0.7321  
Epoch 34/200  
3/3 [==============================] - 0s 355us/step - loss: 0.5570 - accuracy: 0.6964  
Epoch 35/200  
3/3 [==============================] - 0s 385us/step - loss: 0.5546 - accuracy: 0.8071  
Epoch 36/200  
3/3 [==============================] - 0s 375us/step - loss: 0.5616 - accuracy: 0.6714  
Epoch 37/200  
3/3 [==============================] - 0s 379us/step - loss: 0.6955 - accuracy: 0.6464  
Epoch 38/200  
3/3 [==============================] - 0s 389us/step - loss: 0.6089 - accuracy: 0.7321  
Epoch 39/200  
3/3 [==============================] - 0s 375us/step - loss: 0.5377 - accuracy: 0.6714  
Epoch 40/200  
3/3 [==============================] - 0s 392us/step - loss: 0.6224 - accuracy: 0.7179  
Epoch 41/200  
3/3 [==============================] - 0s 379us/step - loss: 0.6234 - accuracy: 0.5464  
Epoch 42/200  
3/3 [==============================] - 0s 411us/step - loss: 0.5224 - accuracy: 0.8321  
Epoch 43/200  
3/3 [==============================] - 0s 386us/step - loss: 0.5848 - accuracy: 0.5964  
Epoch 44/200  
3/3 [==============================] - 0s 401us/step - loss: 0.4620 - accuracy: 0.8679  
Epoch 45/200  
3/3 [==============================] - 0s 365us/step - loss: 0.4664 - accuracy: 0.8071  
Epoch 46/200  
3/3 [==============================] - 0s 367us/step - loss: 0.5904 - accuracy: 0.7679  
Epoch 47/200  
3/3 [==============================] - 0s 359us/step - loss: 0.5111 - accuracy: 0.7929  
Epoch 48/200  
3/3 [==============================] - 0s 363us/step - loss: 0.4712 - accuracy: 0.8679  
Epoch 49/200  
3/3 [==============================] - 0s 401us/step - loss: 0.5601 - accuracy: 0.8071  
Epoch 50/200  
3/3 [==============================] - 0s 429us/step - loss: 0.4884 - accuracy: 0.7929  
Epoch 51/200  
3/3 [==============================] - 0s 377us/step - loss: 0.5137 - accuracy: 0.8286  
Epoch 52/200  
3/3 [==============================] - 0s 368us/step - loss: 0.5475 - accuracy: 0.8286  
Epoch 53/200  
3/3 [==============================] - 0s 592us/step - loss: 0.4077 - accuracy: 0.8536  
Epoch 54/200  
3/3 [==============================] - 0s 400us/step - loss: 0.5367 - accuracy: 0.8179  
Epoch 55/200  
3/3 [==============================] - 0s 399us/step - loss: 0.5288 - accuracy: 0.8429  
Epoch 56/200  
3/3 [==============================] - 0s 367us/step - loss: 0.5775 - accuracy: 0.6964  
Epoch 57/200  
3/3 [==============================] - 0s 372us/step - loss: 0.5680 - accuracy: 0.6821  
Epoch 58/200  
3/3 [==============================] - 0s 360us/step - loss: 0.5164 - accuracy: 0.7321  
Epoch 59/200  
3/3 [==============================] - 0s 364us/step - loss: 0.5334 - accuracy: 0.6571  
Epoch 60/200  
3/3 [==============================] - 0s 358us/step - loss: 0.3858 - accuracy: 0.9036  
Epoch 61/200  
3/3 [==============================] - 0s 356us/step - loss: 0.4313 - accuracy: 0.8679  
Epoch 62/200  
3/3 [==============================] - 0s 373us/step - loss: 0.5017 - accuracy: 0.8429  
Epoch 63/200  
3/3 [==============================] - 0s 346us/step - loss: 0.4649 - accuracy: 0.8429  
Epoch 64/200  
3/3 [==============================] - 0s 397us/step - loss: 0.3804 - accuracy: 0.8893  
Epoch 65/200  
3/3 [==============================] - 0s 361us/step - loss: 0.5030 - accuracy: 0.7929  
Epoch 66/200  
3/3 [==============================] - 0s 372us/step - loss: 0.3958 - accuracy: 0.9286  
Epoch 67/200  
3/3 [==============================] - 0s 345us/step - loss: 0.4240 - accuracy: 0.8536  
Epoch 68/200  
3/3 [==============================] - 0s 360us/step - loss: 0.4651 - accuracy: 0.7929  
Epoch 69/200  
3/3 [==============================] - 0s 376us/step - loss: 0.4687 - accuracy: 0.7571  
Epoch 70/200  
3/3 [==============================] - 0s 398us/step - loss: 0.4660 - accuracy: 0.8429  
Epoch 71/200  
3/3 [==============================] - 0s 368us/step - loss: 0.3960 - accuracy: 0.9393  
Epoch 72/200  
3/3 [==============================] - 0s 355us/step - loss: 0.5523 - accuracy: 0.6071  
Epoch 73/200  
3/3 [==============================] - 0s 361us/step - loss: 0.5266 - accuracy: 0.7821  
Epoch 74/200  
3/3 [==============================] - 0s 371us/step - loss: 0.4245 - accuracy: 0.9643  
Epoch 75/200  
3/3 [==============================] - 0s 367us/step - loss: 0.5024 - accuracy: 0.7786  
Epoch 76/200  
3/3 [==============================] - 0s 453us/step - loss: 0.3419 - accuracy: 0.9393  
Epoch 77/200  
3/3 [==============================] - 0s 405us/step - loss: 0.4930 - accuracy: 0.7429  
Epoch 78/200  
3/3 [==============================] - 0s 672us/step - loss: 0.3443 - accuracy: 0.9036  
Epoch 79/200  
3/3 [==============================] - 0s 386us/step - loss: 0.3864 - accuracy: 0.8893  
Epoch 80/200  
3/3 [==============================] - 0s 386us/step - loss: 0.3863 - accuracy: 0.9286  
Epoch 81/200  
3/3 [==============================] - 0s 391us/step - loss: 0.2771 - accuracy: 0.8679  
Epoch 82/200  
3/3 [==============================] - 0s 370us/step - loss: 0.6083 - accuracy: 0.5571  
Epoch 83/200  
3/3 [==============================] - 0s 387us/step - loss: 0.2801 - accuracy: 0.9393  
Epoch 84/200  
3/3 [==============================] - 0s 357us/step - loss: 0.2483 - accuracy: 0.9286  
Epoch 85/200  
3/3 [==============================] - 0s 355us/step - loss: 0.2511 - accuracy: 0.9643  
Epoch 86/200  
3/3 [==============================] - 0s 339us/step - loss: 0.3410 - accuracy: 0.8893  
Epoch 87/200  
3/3 [==============================] - 0s 361us/step - loss: 0.3432 - accuracy: 0.9036  
Epoch 88/200  
3/3 [==============================] - 0s 347us/step - loss: 0.3819 - accuracy: 0.8893  
Epoch 89/200  
3/3 [==============================] - 0s 361us/step - loss: 0.5142 - accuracy: 0.7179  
Epoch 90/200  
3/3 [==============================] - 0s 502us/step - loss: 0.3055 - accuracy: 0.9393  
Epoch 91/200  
3/3 [==============================] - 0s 377us/step - loss: 0.3144 - accuracy: 0.8536  
Epoch 92/200  
3/3 [==============================] - 0s 376us/step - loss: 0.3712 - accuracy: 0.9036  
Epoch 93/200  
3/3 [==============================] - 0s 389us/step - loss: 0.1974 - accuracy: 0.9393  
Epoch 94/200  
3/3 [==============================] - 0s 365us/step - loss: 0.3128 - accuracy: 0.9393  
Epoch 95/200  
3/3 [==============================] - 0s 376us/step - loss: 0.2194 - accuracy: 1.0000  
Epoch 96/200  
3/3 [==============================] - 0s 377us/step - loss: 0.1994 - accuracy: 1.0000  
Epoch 97/200  
3/3 [==============================] - 0s 360us/step - loss: 0.1734 - accuracy: 0.9643  
Epoch 98/200  
3/3 [==============================] - 0s 367us/step - loss: 0.1786 - accuracy: 1.0000  
Epoch 99/200  
3/3 [==============================] - 0s 358us/step - loss: 0.4158 - accuracy: 0.8286  
Epoch 100/200  
3/3 [==============================] - 0s 354us/step - loss: 0.3131 - accuracy: 0.7571  
Epoch 101/200  
3/3 [==============================] - 0s 350us/step - loss: 0.1953 - accuracy: 0.8893  
Epoch 102/200  
3/3 [==============================] - 0s 403us/step - loss: 0.2577 - accuracy: 0.8429  
Epoch 103/200  
3/3 [==============================] - 0s 417us/step - loss: 0.2648 - accuracy: 0.8893  
Epoch 104/200  
3/3 [==============================] - 0s 377us/step - loss: 0.2901 - accuracy: 0.8286  
Epoch 105/200  
3/3 [==============================] - 0s 383us/step - loss: 0.2822 - accuracy: 0.9393  
Epoch 106/200  
3/3 [==============================] - 0s 381us/step - loss: 0.2837 - accuracy: 0.9036  
Epoch 107/200  
3/3 [==============================] - 0s 382us/step - loss: 0.3064 - accuracy: 0.8536  
Epoch 108/200  
3/3 [==============================] - 0s 352us/step - loss: 0.3376 - accuracy: 0.9036  
Epoch 109/200  
3/3 [==============================] - 0s 376us/step - loss: 0.3412 - accuracy: 0.8536  
Epoch 110/200  
3/3 [==============================] - 0s 363us/step - loss: 0.1718 - accuracy: 1.0000  
Epoch 111/200  
3/3 [==============================] - 0s 347us/step - loss: 0.1899 - accuracy: 0.8786  
Epoch 112/200  
3/3 [==============================] - 0s 363us/step - loss: 0.2352 - accuracy: 0.8286  
Epoch 113/200  
3/3 [==============================] - 0s 373us/step - loss: 0.1378 - accuracy: 1.0000  
Epoch 114/200  
3/3 [==============================] - 0s 353us/step - loss: 0.4288 - accuracy: 0.7071  
Epoch 115/200  
3/3 [==============================] - 0s 456us/step - loss: 0.4202 - accuracy: 0.6821  
Epoch 116/200  
3/3 [==============================] - 0s 382us/step - loss: 0.2962 - accuracy: 0.8893  
Epoch 117/200  
3/3 [==============================] - 0s 394us/step - loss: 0.2571 - accuracy: 0.8893  
Epoch 118/200  
3/3 [==============================] - 0s 365us/step - loss: 0.2697 - accuracy: 1.0000  
Epoch 119/200  
3/3 [==============================] - 0s 358us/step - loss: 0.3102 - accuracy: 0.9036  
Epoch 120/200  
3/3 [==============================] - 0s 367us/step - loss: 0.2928 - accuracy: 0.8286  
Epoch 121/200  
3/3 [==============================] - 0s 374us/step - loss: 0.3157 - accuracy: 0.8286  
Epoch 122/200  
3/3 [==============================] - 0s 381us/step - loss: 0.3920 - accuracy: 0.7786  
Epoch 123/200  
3/3 [==============================] - 0s 335us/step - loss: 0.2090 - accuracy: 0.9036  
Epoch 124/200  
3/3 [==============================] - 0s 368us/step - loss: 0.5079 - accuracy: 0.7786  
Epoch 125/200  
3/3 [==============================] - 0s 337us/step - loss: 0.1900 - accuracy: 0.9393  
Epoch 126/200  
3/3 [==============================] - 0s 339us/step - loss: 0.2047 - accuracy: 0.9643  
Epoch 127/200  
3/3 [==============================] - 0s 479us/step - loss: 0.3705 - accuracy: 0.7679  
Epoch 128/200  
3/3 [==============================] - 0s 390us/step - loss: 0.1850 - accuracy: 0.9036  
Epoch 129/200  
3/3 [==============================] - 0s 642us/step - loss: 0.1594 - accuracy: 0.9393  
Epoch 130/200  
3/3 [==============================] - 0s 373us/step - loss: 0.2010 - accuracy: 0.8893  
Epoch 131/200  
3/3 [==============================] - 0s 369us/step - loss: 0.0849 - accuracy: 1.0000  
Epoch 132/200  
3/3 [==============================] - 0s 349us/step - loss: 0.1145 - accuracy: 1.0000  
Epoch 133/200  
3/3 [==============================] - 0s 360us/step - loss: 0.1796 - accuracy: 1.0000  
Epoch 134/200  
3/3 [==============================] - 0s 371us/step - loss: 0.2363 - accuracy: 0.8536  
Epoch 135/200  
3/3 [==============================] - 0s 386us/step - loss: 0.1922 - accuracy: 0.9393  
Epoch 136/200  
3/3 [==============================] - 0s 369us/step - loss: 0.3595 - accuracy: 0.7679  
Epoch 137/200  
3/3 [==============================] - 0s 369us/step - loss: 0.1506 - accuracy: 0.8893  
Epoch 138/200  
3/3 [==============================] - 0s 377us/step - loss: 0.2471 - accuracy: 0.8536  
Epoch 139/200  
3/3 [==============================] - 0s 417us/step - loss: 0.1768 - accuracy: 0.8536  
Epoch 140/200  
3/3 [==============================] - 0s 400us/step - loss: 0.2112 - accuracy: 0.9393  
Epoch 141/200  
3/3 [==============================] - 0s 377us/step - loss: 0.3652 - accuracy: 0.7179  
Epoch 142/200  
3/3 [==============================] - 0s 364us/step - loss: 0.3007 - accuracy: 0.8429  
Epoch 143/200  
3/3 [==============================] - 0s 361us/step - loss: 0.0518 - accuracy: 1.0000  
Epoch 144/200  
3/3 [==============================] - 0s 373us/step - loss: 0.2144 - accuracy: 0.8286  
Epoch 145/200  
3/3 [==============================] - 0s 353us/step - loss: 0.0888 - accuracy: 1.0000  
Epoch 146/200  
3/3 [==============================] - 0s 361us/step - loss: 0.1267 - accuracy: 1.0000  
Epoch 147/200  
3/3 [==============================] - 0s 341us/step - loss: 0.0321 - accuracy: 1.0000  
Epoch 148/200  
3/3 [==============================] - 0s 358us/step - loss: 0.0860 - accuracy: 1.0000  
Epoch 149/200  
3/3 [==============================] - 0s 375us/step - loss: 0.2151 - accuracy: 0.8893  
Epoch 150/200  
3/3 [==============================] - 0s 351us/step - loss: 0.1592 - accuracy: 1.0000  
Epoch 151/200  
3/3 [==============================] - 0s 531us/step - loss: 0.1450 - accuracy: 0.8786  
Epoch 152/200  
3/3 [==============================] - 0s 392us/step - loss: 0.1813 - accuracy: 0.9036  
Epoch 153/200  
3/3 [==============================] - 0s 404us/step - loss: 0.1197 - accuracy: 1.0000  
Epoch 154/200  
3/3 [==============================] - 0s 367us/step - loss: 0.0930 - accuracy: 1.0000  
Epoch 155/200  
3/3 [==============================] - 0s 580us/step - loss: 0.2587 - accuracy: 0.8893  
Epoch 156/200  
3/3 [==============================] - 0s 383us/step - loss: 0.0742 - accuracy: 1.0000  
Epoch 157/200  
3/3 [==============================] - 0s 353us/step - loss: 0.1197 - accuracy: 0.9643  
Epoch 158/200  
3/3 [==============================] - 0s 371us/step - loss: 0.1716 - accuracy: 0.8536  
Epoch 159/200  
3/3 [==============================] - 0s 337us/step - loss: 0.1300 - accuracy: 0.9643  
Epoch 160/200  
3/3 [==============================] - 0s 347us/step - loss: 0.1439 - accuracy: 0.9393  
Epoch 161/200  
3/3 [==============================] - 0s 366us/step - loss: 0.2597 - accuracy: 0.9393  
Epoch 162/200  
3/3 [==============================] - 0s 345us/step - loss: 0.1605 - accuracy: 0.8893  
Epoch 163/200  
3/3 [==============================] - 0s 468us/step - loss: 0.0437 - accuracy: 1.0000  
Epoch 164/200  
3/3 [==============================] - 0s 372us/step - loss: 0.0376 - accuracy: 1.0000  
Epoch 165/200  
3/3 [==============================] - 0s 391us/step - loss: 0.0474 - accuracy: 1.0000  
Epoch 166/200  
3/3 [==============================] - 0s 378us/step - loss: 0.3225 - accuracy: 0.7786  
Epoch 167/200  
3/3 [==============================] - 0s 368us/step - loss: 0.0770 - accuracy: 1.0000  
Epoch 168/200  
3/3 [==============================] - 0s 367us/step - loss: 0.5629 - accuracy: 0.7786  
Epoch 169/200  
3/3 [==============================] - 0s 359us/step - loss: 0.0177 - accuracy: 1.0000  
Epoch 170/200  
3/3 [==============================] - 0s 370us/step - loss: 0.1167 - accuracy: 1.0000  
Epoch 171/200  
3/3 [==============================] - 0s 349us/step - loss: 0.1313 - accuracy: 1.0000  
Epoch 172/200  
3/3 [==============================] - 0s 337us/step - loss: 0.0852 - accuracy: 0.9393  
Epoch 173/200  
3/3 [==============================] - 0s 375us/step - loss: 0.0545 - accuracy: 1.0000  
Epoch 174/200  
3/3 [==============================] - 0s 354us/step - loss: 0.0674 - accuracy: 0.9643  
Epoch 175/200  
3/3 [==============================] - 0s 355us/step - loss: 0.0911 - accuracy: 1.0000  
Epoch 176/200  
3/3 [==============================] - 0s 404us/step - loss: 0.0980 - accuracy: 0.9393  
Epoch 177/200  
3/3 [==============================] - 0s 396us/step - loss: 0.0465 - accuracy: 1.0000  
Epoch 178/200  
3/3 [==============================] - 0s 403us/step - loss: 0.1117 - accuracy: 0.9393  
Epoch 179/200  
3/3 [==============================] - 0s 373us/step - loss: 0.0415 - accuracy: 1.0000  
Epoch 180/200  
3/3 [==============================] - 0s 369us/step - loss: 0.0825 - accuracy: 1.0000  
Epoch 181/200  
3/3 [==============================] - 0s 425us/step - loss: 0.0378 - accuracy: 1.0000  
Epoch 182/200  
3/3 [==============================] - 0s 381us/step - loss: 0.1155 - accuracy: 0.9393  
Epoch 183/200  
3/3 [==============================] - 0s 354us/step - loss: 0.0207 - accuracy: 1.0000  
Epoch 184/200  
3/3 [==============================] - 0s 346us/step - loss: 0.0344 - accuracy: 1.0000  
Epoch 185/200  
3/3 [==============================] - 0s 379us/step - loss: 0.0984 - accuracy: 0.9393  
Epoch 186/200  
3/3 [==============================] - 0s 360us/step - loss: 0.1508 - accuracy: 0.8536  
Epoch 187/200  
3/3 [==============================] - 0s 361us/step - loss: 0.0463 - accuracy: 1.0000  
Epoch 188/200  
3/3 [==============================] - 0s 358us/step - loss: 0.0476 - accuracy: 0.9643  
Epoch 189/200  
3/3 [==============================] - 0s 379us/step - loss: 0.1592 - accuracy: 1.0000  
Epoch 190/200  
3/3 [==============================] - 0s 387us/step - loss: 0.0071 - accuracy: 1.0000  
Epoch 191/200  
3/3 [==============================] - 0s 405us/step - loss: 0.0527 - accuracy: 1.0000  
Epoch 192/200  
3/3 [==============================] - 0s 401us/step - loss: 0.0874 - accuracy: 0.9393  
Epoch 193/200  
3/3 [==============================] - 0s 355us/step - loss: 0.0199 - accuracy: 1.0000  
Epoch 194/200  
3/3 [==============================] - 0s 373us/step - loss: 0.1299 - accuracy: 0.9643  
Epoch 195/200  
3/3 [==============================] - 0s 360us/step - loss: 0.0929 - accuracy: 1.0000  
Epoch 196/200  
3/3 [==============================] - 0s 380us/step - loss: 0.0265 - accuracy: 1.0000  
Epoch 197/200  
3/3 [==============================] - 0s 358us/step - loss: 0.0843 - accuracy: 1.0000  
Epoch 198/200  
3/3 [==============================] - 0s 354us/step - loss: 0.0925 - accuracy: 1.0000  
Epoch 199/200  
3/3 [==============================] - 0s 327us/step - loss: 0.0770 - accuracy: 1.0000  
Epoch 200/200  
3/3 [==============================] - 0s 561us/step - loss: 0.0311 - accuracy: 1.0000  
found in bag: 你好  
[0 0 1 0 0 0 0 0 0 0 0 0 0 0]  
found in bag: 请问  
[('打招呼', '0.998965')]

瞬间执行完毕,秒杀intel芯片的mac,怎一个香字了得!

接下来,尝试安装另外一个在业界名声煊赫的深度学习框架Pytorch!

由于当前arm64架构的只支持3.9版本,所以我们来创建一个虚拟空间:

sudo conda create -n pytorch numpy matplotlib pandas python=3.9

这里提前将需要的基础库都一一安装,因为如果不在创建虚拟空间时提前安装,之后使用pip是安装不上的,安装成功后,激活环境:

(pytorch) ➜  conda activate pytorch                                          
(pytorch) ➜

随后下载arm64版本的pytorch安装包:https://github.com/wizyoung/A...\_11\_0\_arm64.whl

下载成功后,执行安装命令:

sudo pip install torch-1.8.0a0-cp39-cp39-macosx_11_0_arm64.whl

让我们来试试Pytorch在M1芯片加持后的性能,编写test\_torch.py:

from tqdm import tqdm  
import torch  
  
@torch.jit.script  
def foo():  
    x = torch.ones((1024 * 12, 1024 * 12), dtype=torch.float32)  
    y = torch.ones((1024 * 12, 1024 * 12), dtype=torch.float32)  
    z = x + y  
    return z  
  
  
if __name__ == '__main__':  
    z0 = None  
    for _ in tqdm(range(10000000000)):  
        zz = foo()  
        if z0 is None:  
            z0 = zz  
        else:  
            z0 += zz

结果依然秒杀Intel mac,达到了320 it/s,你敢信?

最后,有没有arm64架构的编辑器呢?答案是有的,vscode值得拥有,下载地址:https://code.visualstudio.com... 一定要选择arm64版的:

解压后直接运行即可,可以在插件商店选择Python和Code Runner,即可开启M1的Python代码编写之旅。

结语:M1芯片的Mac和Python3,简直就是金风玉露,绝配天成。只要撩开M1和开发者们之间的那一层帷幔,等待我们的,就是纵享丝滑的开发感受,还等什么?犹豫只会败北,是时候燃烧灵魂,献出钱包了。

原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_189

查看原文

赞 0 收藏 0 评论 0

刘悦的技术博客 收藏了文章 · 3月2日

讲技术-什么是Web Hook?

(一、)什么是Web Hook?

Webhook是一个API概念,并且变得越来越流行。我们能用事件描述的事物越多,webhook的作用范围也就越大。Webhook作为一个轻量的事件处理应用,正变得越来越有用。

简单来说就是一种反向API机制,类似于触发器的一样.

场景模拟:

在传统的web server设计中,我们项目A想要获取项目B的数据,通常项目B需要提供一个API,然后项目A去请求项目B的API,从而获得数据,这样的过程我们称之为"拉"数据。

==通过webhook机制,对客户端-服务端的模式进行了逆转。==

继续回到场景中:

新增了一个需求,项目A需要实时获取到项目B的最新数据,在传统做法中,我们需要不停的去向项目B做轮询操作,以便获取到最新数据,这样的效率和性能都非常低下,通过webhook机制来设计

传统做法:项目A需要不停轮询去拉取项目B的最新数据

项目A   -> 项目B

webhook机制:项目A提供一个webhook url,每次项目B创建新数据时,便会向项目A的hook地址进行请求,项目A收到项目B的请求,然后对数据进行处理

项目B   -> 项目A

用一张图来说明,这个流程.
image

(二、)如何使用webhook?

使用webhook就需要为对应的服务端设计一个hook url,用于接收服务端的请求。

例如:

http://www.abcd.com/api/video/hook

通常webhook请求过来的数据格式为xml和json两种,在现代Web应用中,都能很好的解析和对这两种数据进行交互.

(三、)主要应用场景

都常应用于异步编程中,如:高安全的支付、微信登录(OAuth)、资源同步、资源创建与更新、耗时较长。

场景A:
你需要向视频处理服务器上传一个视频、你需要获得视频处理后的结果,然而视频处理服务器上视频很多,在排队进行处理,你不能立刻获取到视频的处理结果,此时你可以设计一个hook url,当视频处理完成后,视频处理服务器自动向你的hook url发送请求,告诉你视频已经处理完毕.

(四、)安全问题

由于webhook会向公网上的hook url发送数据,这就意味着某些不好心的人可能会找到这个url,从而进行发送错误的数据,但是我们可以通过以下一些技术手段来解决这个问题:

  • 增加token机制
  • 增加auth认证
  • 只接收对应服务端domain或IP请求
  • 数据签名

(五、)需要注意的问题

当我们使用web hook机制需要特别注意的问题

  1. 当服务提供者通过web hook将数据发送你的服务端后,就不会再去关注这些数据。假设你的服务端此时出现了崩溃,或者无法请求成功等原因,就需要主动去尝试请求数据。
  2. webhook会发出大量的请求,可能会造成你的应用阻塞,在此需要确保你的应用能够处理好这些请求。
查看原文

刘悦的技术博客 发布了文章 · 3月1日

别梦依稀咒逝川,Ruby二十八年前|M1 Mac os配置Ruby(3.0.0) on Rails(6.1.1)(2021最新)

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_188

在每个开发者心里,都会有一门“最好”的语言,在这个世界的某个深处,在一些矫矫不群的人们心中,这门语言的名字叫做Ruby,它今年二十八岁了,历史和Java一样的悠久,但是它没有大厂背书、它的性能被开发者诟病、时至今日依然无法高效利用多核资源,甚至于它每年都要被“死亡”一次,相比于有太阳计算机系统、甲骨文、IBM 这些大公司支持的 Java,它是那么的一无所有,但是,它又拥有全世界最虔诚的“信徒”,拥有最活跃的开发者社区,这一切,又让它是那么的应有尽有。是的,这就是Rubyist的理念:有的时候,你想证明给一万个人看,到后来,你发现只得到了一个明白的人,那就够了。

本次我们尝试在最新的M1芯片Mac os(Big Sur 11.2.2)中搭建最新版Ruby3.0.0以及Web开发框架Rails6.1.1,全新的芯片、全新的征途、全新的开始:

首先我们来看看M1芯片的命令行,如果你是从老版本Mac迁移过来的,比如笔者(Mojave),最好将老的Base命令行更换成zsh,zsh是一款功能比bash更强大的终端(shell)系统,既可以作为一个交互式终端,也可以作为一个脚本解释器,这里更换必要性是指如果使用Bash编译Ruby3.0,可能会发生一些未知错误。执行命令切换zsh:

sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

安装成功后,确保在应用程序-》实用工具-》终端-》简介中,不要勾选Rosetta,因为接下来我们需要以arm架构的homebrew进行安装,所以所有的编译和运行动作都不需要Rosetta的参与:

随后重启终端,开始安装amr架构的Homebrew:

/bin/bash -c "$(curl -fsSL https://cdn.jsdelivr.net/gh/ineo6/homebrew-install/install.sh)"

然后编辑配置文件 ~/.zshrc,加入如下内容:

path=('/opt/homebrew/bin' $path)  
export PATH

存盘之后执行命令:

source ~/.zshrc

查看新brew的位置:

➜  ~ which brew  
/opt/homebrew/bin/brew

如果返回的是/opt/homebrew/bin/brew就说明安装成功,接着更新一下版本:

➜  ~ brew cleanup && brew update  
Already up-to-date.

如果没有代理,可以选择设置一下国内源:

# brew  
git -C "$(brew --repo)" remote set-url origin https://mirrors.ustc.edu.cn/brew.git  
  
# core  
git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git  
  
# cask  
git -C "$(brew --repo homebrew/cask)" remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git  
  
  
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles' >> ~/.zprofile  
source ~/.zprofile

接下来我们来安装Ruby3.0,业界比较主流的安装方式大抵两种:rvm或者rbenv,这里我们使用rbenv,它其实就是一个类似python中conda一样的多版本管理软件包,可以方便一些老项目以低版本ruby运行,比如ruby2.6。

Ruby 依赖 OpenSSL和AutoConf这俩个包,提前预装好,如果是迁移过来的Openssl可能版本比较低,最好重新安装最新的1.1j版本:

brew reinstall openssl@1.1  
brew reinstall autoconf

随后安装rbenv,执行命令:

brew install ruby-build rbenv

之后将rbenv命令添加到zsh命令行的环境变量中:

echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.zshrc  
source ~/.zshrc

重启命令行,键入rbenv:

➜  ~ rbenv  
rbenv 1.1.2  
Usage: rbenv <command> [<args>]  
  
Some useful rbenv commands are:  
   commands    List all available rbenv commands  
   local       Set or show the local application-specific Ruby version  
   global      Set or show the global Ruby version  
   shell       Set or show the shell-specific Ruby version  
   install     Install a Ruby version using ruby-build  
   uninstall   Uninstall a specific Ruby version  
   rehash      Rehash rbenv shims (run this after installing executables)  
   version     Show the current Ruby version and its origin  
   versions    List installed Ruby versions  
   which       Display the full path to an executable  
   whence      List all Ruby versions that contain the given executable

如果返回版本号和相关操作,问题就不大了,不过最好通过脚本诊断一下,确保后续编译不会出问题:

curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

诊断没有报错误即可:

➜  ~ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash  
Checking for `rbenv' in PATH: /opt/homebrew/bin/rbenv  
Checking for rbenv shims in PATH: OK  
Checking `rbenv install' support: multiple  
  You seem to have multiple `rbenv-install' in the following locations.  
  Please pick just one installation and remove the others.  
    
  /Users/liuyue/.rbenv/plugins/ruby-build/bin/rbenv-install  
  /opt/homebrew/bin/rbenv-install  
  
Counting installed Ruby versions: 1 versions  
Checking RubyGems settings: OK  
Auditing installed plugins: OK

接下来,由于众所周知的学术问题,rbenv下载二进制安装包会非常的缓慢,所以我们可以通过国内镜像来手动下载:https://cache.ruby-china.com/...

这里下载ruby3.0正式版:

然后将压缩包手动拷贝到rbenv的安装目录:~/.rbenv/cache

这里的~/.rbenv/cache有可能不存在,可以手动创建:

mkdir ~/.rbenv/cache

拷贝安装包:

cp ~/Downloads/ruby-3.0.0.tar.gz ~/.rbenv/cache/ruby-3.0.0.tar.gz

紧接着我们终于可以安装Ruby3.0本体了:

brew link openssl --force  
  
RUBY_CONFIGURE_OPTS=--with-openssl-dir=/opt/homebrew/Cellar/openssl@1.1/1.1.1j  rbenv install 3.0.0

由于笔者之前安装过openssl,所以这次强制指定由arm架构的openssl来编译安装。

安装成功后键入rbenv versions:

➜  ~ rbenv versions       
* system (set by /Users/liuyue/.rbenv/version)  
  3.0.0

可以看到,除了M1系统默认的版本,又出现了一个3.0.0版本,我们可以使用 rbenv global命令来切换版本:

➜  ~ rbenv global 3.0.0   
➜  ~ rbenv versions  
  system  
* 3.0.0 (set by /Users/liuyue/.rbenv/version)

随后输入ruby -v:

➜  ~ ruby -v  
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [arm64-darwin20]

可以看到版本已经切换到3.0,并且内核版本是arm64位,如果需要系统默认版本,还可以切回来:

➜  ~ rbenv global system  
➜  ~ ruby -v              
ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.arm64e-darwin20]

系统默认是ruby 2.6.3,最好不要动它。

接着我们就可以安装Rails了:

gem install rails -v 6.1.1

安装成功后,刷新一下:

rbenv rehash

然后查看版本号:

➜  ~ rails -v  
Rails 6.1.1  
➜  ~

创建一个新项目:

rails new myrails

进入项目目录:

cd myrails

启动服务:

rails s

千呼万唤始出来:

结语:作为同龄语言,如果说Java是闪现在天上的瑰丽,那么Ruby就是埋藏于地底的炽热,同样伟大但各擅胜场,而事实上同样作为脚本语言的Ruby更多的是在和Python对比,Python近几年在数据分析和深度学习领域的突飞猛进让Ruby难以望其项背,而Ruby在Mac系统中软件包管理层面却有着统治级的地位,就像你玩儿Mac就避免不了Homebrew,接触Homebrew就无法躲开Ruby。很多人唱衰Ruby,认为它过时了,而在Rubyist的心中则正相反,它太超前了,正是和M1芯片一样,是超越时代的产物,也许有一天,它会“死亡”,但绝不会是今天,最后,用十九世纪美国小说家赫尔曼·梅尔维尔《白鲸》中的一节和诸君共勉:

“有些人死在退潮里;有些人死在浅水滩里;有些人却死在洪水里。” ——第一百三十五章,亚哈最后一次追击白鲸时,对阻拦他的大副斯达巴克说。

原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_188

查看原文

赞 0 收藏 0 评论 0

刘悦的技术博客 发布了文章 · 2月24日

无意苦争春,一任群芳妒!M1 Mac book能否支撑全栈工程师的日常?(Python3/虚拟机/Docker/Redis)

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_187
image

就像大航海时代里突然诞生的航空母舰一样,苹果把玩着手心里远超时代的M1芯片,微笑着对Intel说:“不好意思,虽然你也玩桌面芯片,但是,从今天开始,游戏就已经结束了,X86?还是省省吧。”

十五年前,iPhone横空出世,乔布斯告诉世人什么才叫做真正的智能手机,十年前,A4处理器粉墨出场,iPhone瞬间猛虎添翼,性能这两个字从此没有友商敢在苹果面前提及,iPad更是在业界呼风唤雨,几乎把整个平板市场都收入囊中,在人们的脑海中,Pad就是iPad的代名词,安卓平板?不存在的。今时今日,Apple Silicon华丽登台,不必说M1堪称恐怖到爆炸的性能,也不必说十个小时以上的超长续航以及丝滑无迟滞的FCPX极速剪辑体验,单是通过Rosetta 2 虚拟运行X86应用,就已经足以让人感到惊艳了。然而,这些福利只是苹果对于产品级用户的馈赠,作为一名开发者,而且是全栈(全干)开发,M1能否应对繁复的开发环境?就像一位研发同事说的那样:“Hi,哥儿们,你不会花一万大洋买了个ipad+秒控键盘吧?这智商税有点贵啊”。

首先是Python3的开发环境,python官网已经释出适配M1芯片的3.9版本,https://www.python.org/downlo... ,但其实目前生态和市场占有率最高的还是3.7,笔者是通过迁移助理将老mac book pro里的开发环境(x86)直接迁移过来的,也就是迁移了原电脑的python3.7.4,令人意外的是,通过Rosetta 2,大部分的基础库都可以使用,除了首次运行稍显迟滞以外:

liuyue:~ liuyue$ python3  
Python 3.7.4 (v3.7.4:e09359112e, Jul  8 2019, 14:54:52)   
[Clang 6.0 (clang-600.0.57)] on darwin  
Type "help", "copyright", "credits" or "license" for more information.  
>>> import pandas  
>>> import cv2  
>>> import numpy  
>>> import matplotlib  
>>> import nltk  
>>> import ssl  
>>> from nltk.stem.lancaster import LancasterStemmer

但是一些深度学习框架比如TensorFlow就不行了,解决方案还是得从官网下载适配M1的3.9版本,然后再下载支持M1芯片的Tensorflow2.4,下载链接:https://link.zhihu.com/?targe...\_and\_install.sh,换句话说,想用TensorFlow训练模型你就得升级python版本,可是很多TensorFlow项目都是和Web应用结合使用的,如果单独为了TensorFlow升级python,就需要将原来的项目分开部署,接口也得重构,尤其一些“祖传项目”就更不好弄了,这无疑提高了开发者的开发成本。

再来说说测试,自动化测试工具selenium是我们经常使用的工具之一,使用场景非常广泛,但是在M1系统里面我们通过Rosetta 2虚拟的python3.7能否控制arm64版本的chrome呢?答案是可以的,因为chromedriver已经对M1芯片进行了适配,下载驱动:http://npm.taobao.org/mirrors... 注意要选择m1版本的驱动,随后解压,将chromedriver文件拷贝到系统目录中:

sudo mv chromedriver /usr/local/bin

随后启动浏览器:

from selenium import webdriver  
import time  
  
  
#selenium 截图  
driver = webdriver.Chrome()  
driver.get('https://v3u.cn')  
time.sleep(3)
driver.close()

问题并不大,所以如果单纯用M1 Mac用来写自动化测试脚本或者爬虫不会有太大压力,但是,如果涉及科学计算的结果测试,可能就得折腾一下了。

再聊聊虚拟机,虚拟机无论是测试岗还是运维岗基本都会用到,因为有些应用在Win和Mac系统中展示出的效果不尽相同,目前虚拟机两大巨头Vmware和Parallels都在加紧适配,Parallels领先一步,首先释出了兼容M1芯片的测试版虚拟机,不过需要先注册获取注册码:https://b2b.parallels.com/app...

随后在该页面下载安装包进行安装即可,需要注意的一点是,注册的时候一定要记录一下注册码,否则安装成功后没有码进行激活就尴尬了。

与此同时微软也适时的发布了基于arm内核的win10测试版镜像,下载地址:https://www.microsoft.com/en-... 当然了,因为一些众所周知的学术问题,导致下载速度不尽如人意,笔者已经将安装包和镜像上传网盘,在文章底部会贴出来。

安装好Parallels以后,将arm版win10镜像导入即可安装,只需要5分钟左右就可以安装成功:

虽然是测试版,但是整体上运行起来非常行云流水,别忘了在系统内添加中文默认语言包:

在M1 Mac中玩虚拟机,几乎没有迟滞感,顺滑的令人咋舌,风扇纹丝不动,且能耗也并没有显著增加,续航也没有因为虚拟机的出现而减少太多,这是M1非常惊艳的一点。

想要用Mac做开发,包管理工具Homebrew是无法绕过的一环,所以如果你是通过迁移助理把老的x86架构的直接迁移到M1的mac中,那么最好别用了,会出很多问题,解决方案就是安装 arm版的Homebrew

执行安装命令:

/bin/bash -c "$(curl -fsSL https://cdn.jsdelivr.net/gh/ineo6/homebrew-install/install.sh)"

基于arm的Homebrew统一安装在系统的/opt/homebrew目录,主要是为了和老版的x86区别开。

随后添加环境变量:

echo export PATH=/opt/homebrew/bin:$PATH >> ~/.bash_profile   
source ~/.bash_profile

接着执行:

liuyue:~ liuyue$ brew update  
Already up-to-date.  
liuyue:~ liuyue$

如果返回Already up-to-date.就没问题了,基本上目前Homebrew大概有7成左右的软件支持M1芯片,具体适配列表可以参照:https://github.com/Homebrew/b...

可以看到,像数据库如mysql、redis都进行了适配,mysql还有一些小bug,介意的同学可以考虑Rosetta,下面我们来看看用arm版的Homebrew来安装reids的流程:

安装命令:

brew install redis

一般brew会获取最新稳定版,安装成功后,查看软件列表:

liuyue:~ liuyue$ brew list  
openssl@1.1    redis  
liuyue:~ liuyue$

此时启动服务:

liuyue:bin liuyue$ redis-server  
36148:C 24 Feb 2021 21:13:48.673 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo  
36148:C 24 Feb 2021 21:13:48.673 # Redis version=6.2.0, bits=64, commit=00000000, modified=0, pid=36148, just started  
36148:C 24 Feb 2021 21:13:48.673 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf  
36148:M 24 Feb 2021 21:13:48.673 * Increased maximum number of open files to 10032 (it was originally set to 256).  
36148:M 24 Feb 2021 21:13:48.673 * monotonic clock: POSIX clock_gettime  
                _._                                                    
           _.-``__ ''-._                                               
      _.-``    `.  `_.  ''-._           Redis 6.2.0 (00000000/0) 64 bit  
  .-`` .-```.  ```\/    _.,_ ''-._                                     
 (    '      ,       .-`  | `,    )     Running in standalone mode  
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379  
 |    `-._   `._    /     _.-'    |     PID: 36148  
  `-._    `-._  `-./  _.-'    _.-'                                     
 |`-._`-._    `-.__.-'    _.-'_.-'|                                    
 |    `-._`-._        _.-'_.-'    |           http://redis.io          
  `-._    `-._`-.__.-'_.-'    _.-'                                     
 |`-._`-._    `-.__.-'    _.-'_.-'|                                    
 |    `-._`-._        _.-'_.-'    |                                    
  `-._    `-._`-.__.-'_.-'    _.-'                                     
      `-._    `-.__.-'    _.-'                                         
          `-._        _.-'                                             
              `-.__.-'

然后开启另一个命令行连接服务:

liuyue:~ liuyue$ redis-cli  
127.0.0.1:6379> ping  
PONG  
127.0.0.1:6379>

可以看到和x86的brew用起来没有区别,只不过上面的软件或多或少有些bug,用起来要小心。

如果你觉得arm版的brew速度过慢,可以单独设置国内源:

git -C "$(brew --repo)" remote set-url origin https://mirrors.ustc.edu.cn/brew.git  
  
  
git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git  
  
  
git -C "$(brew --repo homebrew/cask)" remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git  
  
  
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles' >> ~/.bash_profile  
source ~/.bash_profile

中科大的镜像稍微靠谱一点。

有同学说,那还有三成的软件怎么搞?另外看起来arm的brew并不打算适配老版本软件,比如python3.7,其实在M1 的mac系统也可以装一版x86的brew,安装 x86 的 Homebrew:

arch -x86_64 /bin/bash -c "$(curl -fsSL https://cdn.jsdelivr.net/gh/ineo6/homebrew-install/install.sh)"

装好之后,它还是在原来的/usr/local/bin/brew里面,这样就和/opt/homebrew/bin/brew 区分开了

执行命令:



liuyue:~ liuyue$ /usr/local/bin/brew list  
autoconf    gmp        libtool        openssl@1.1    siege  
automake    icu4c        libyaml        perl        sqlite  
boost        isl        lua        pkg-config    thrift  
coreutils    libevent    mkcert        python@3.8    trash  
gcc        libgpg-error    mpfr        readline    xz  
gdbm        libksba        mtr        redis        yarn  
gettext        libmpc        node        ruby        zlib

可以看到这些都是我从老的x86电脑中迁移过来的软件,大部分都用不了,比如redis,而基于arm的brew安装的新redis是可以使用的:

liuyue:~ liuyue$ /opt/homebrew/bin/brew list  
openssl@1.1    redis

所以用M1 mac做开发,就有点分列,新软件包记住用/opt/homebrew/bin/brew,老的用:/usr/local/bin/brew

接着来看看Docker,Docker官方对于M1的适配还是很重视的,早些时候放出来一个测试版客户端:https://docs.docker.com/docke...

不仅可以拉取arm版的镜像,也可以操作Kubernetes,对于Kubernetes不熟悉的同学可以参照这篇文章:一寸宕机一寸血,十万容器十万兵|Win10/Mac系统下基于Kubernetes(k8s)搭建Gunicorn+Flask高可用Web集群

不过令人遗憾的是,Docker Hub 支持 arm 版本的镜像并不多,大抵在x86镜像的三、四成左右,但是可以看出来各大厂商对于M1都是比较看重的,基本上官方都有适配的消息流出。

对于其他的语言来说,比如 go lang、ruby、以及php等,都有一个和python一样的通病,就是老版本不支持arm内核,只适配了最近的一个或者两个版本,这样也导致如果你换电脑的话,就得考虑向下兼容性问题。

对于一些常用的开发软件,比如vscode 和 sublime 3 都可以正常运行,php套件xampp运行无障碍,版本控制客户端SourceTree、数据库客户端Navicat、微信、QQ、SSH客户端iTerm 4以及Ftp客户端FileZilla都可以正常运行,前端微信小程序开发工具还是会有几率闪退,腾讯一开始对big sur就适配的不好,M1就更别提了。

值得一提的是,设计软件Photoshop最新版2021暂时还不支持M1芯片,只能凑合先用2019版的,与之形成对比的就是FCPX10.5的完美表现,看来还是亲儿子给力,Adobe还得加把劲了。

综上,如果您是一个初学者,只是想学习一些语言的基础语法,没打算写一些复杂工业级项目,可以考虑入手M1,它将会是您入门的好帮手,但是如果您在业内已经混迹多年,经常编译一些复杂代码、各种库,甚至手里还有一些“祖传代码”项目在维护,那么x86的Mac可能还得陪您一两年。

最后,上面提到的适配M1芯片的安装包和镜像请移步:

链接: https://pan.baidu.com/s/12d7fHl\_ZYyx4Xk-v0R0N\_g 密码: 1fdc 需要的同学自行下载

结语:有时候,当我们称赞一项技术的时候,我们会称其为这样或者那样的行业标杆、教科书之类,但是对于M1的Mac book来说,它好像已经超越了所谓的标杆,而成为了跨越时代的不朽经典。苹果作为业内完美的六边形战士,还依然在探索,依然没有裹足不前,继续探索新的道路,继续钻研可行性,这是它带给我们的启示,然而,在2021年的这个春天里,面对开发者,M1芯片有点像悲情的俄尔普斯,它能用动人的绕梁琴声打动开发者们,却在最后一秒钟忍不住回眸,断送挽救爱妻欧莉蒂克的最后机会,很明显,它已经足够努力,但是还差那么一点点,就差一点了,但是我相信,神明早已在M1的命格中写下四个字:注定辉煌。

原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_187

查看原文

赞 1 收藏 0 评论 0

刘悦的技术博客 发布了文章 · 2月21日

人理解迭代,神则体会递归,从电影艺术到Python代码实现神的逆向思维模式

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_186
image
“从来如此,便对么?”,鲁迅先生在《狂人日记》中借狂人之口在月光下发出的质疑与呐喊,是的,从来如此,一般人的思维模式就是从来如此,以高数为例子,我们大抵都是先从数分、线代、解几去学泛函、抽代、拓扑等,其实就是按照标准路子来,这样做理论上可以增加对已学知识的理解程度,并对某些数分、线代中的问题看清其本质有所帮助。数学归纳法其实就是一种迭代(iteration),从一个简单的起点,推广到一般情况。而递归(recursion),则是一种反人类的逆向思维模式,作为研发人员,掌握这种反常识的思维逻辑是非常必要的,这里我们以一个推理故事为开端:

在一个秋意绵绵的雨夜,日本警视厅搜查课警司古畑任三郎和其助手今泉驾着小汽车行驶在山路上,突然,汽车抛锚了,无奈之下,古畑只好下车徒步寻求帮助,不远处一所别墅的灯光吸引了他,古畑敲响了别墅的大门,打开门的是美女漫画家千奈美,这里正是她的私人别墅,千奈美得知来的是一位刑警,便告诉了古畑一个可怕的事实,在她私人别墅的仓库里,有一具尸体,这具尸体不是谁,正是漫画出版社的编辑畑野先生,死者面朝下趴在地面上,尸体的额头上有碰伤的痕迹,周围散落着一些稿纸。他把其中的一张稿纸仅仅捏在手里,而纸上却没留下任何遗言之类的东西,仅仅是一张白纸,而恰巧,旁边就有一只打开笔帽的钢笔,在简单勘察现场之后,古畑问起千奈美和畑野的关系,千奈美说他们仅仅是工作上的关系。但因为经常到这儿来商讨出版事宜,所以她给了畑野一把钥匙。

随后,千奈美微笑解释到,畑野先生可能死于意外,仓库的门一旦关上,从里面是打不开的。畑野的死可能就是因为不小心把自己关在了里面,死因是由于缺氧窒息,她自己已经一个多月没有来到这所别墅了,也正是刚刚,她才发现了畑野的尸体。古畑则不这么认为,他怀疑这是一起谋杀。因为死者头部有伤痕,流了血。千奈美认为不可能,因为门是关上的,没有钥匙打不开,只有她和畑野先生有别墅的钥匙,别人不可能进来打破畑野的头,而自己又已经一个多月没有来过了,古畑此时起了疑心,他认为千奈美的说法不合常理。好比有一罐饼干,我们打开后发现有一块被人咬了一口,通常情况下,我们都会推测是有人先咬了饼干再盖上盖子的。聪明的古畑发现千奈美言行有疑点,于是又折回仓库,在死者的口袋里发现几张三天前的购物发票,据此,古畑分析道:死者肯定知道凶手的名字,因为三天前死者和凶手一同来到别墅,而死者身边肯定藏着揭穿凶手真相的线索,为什么死者会在这么多的稿纸中,紧紧抓住这一张空白的稿纸呢?明明有纸有笔,却什么都没有写。他到底想表达什么呢?古畑费解地思考着。
此时,雷电交加,屋内突然停电了。千奈美忍不住叹息道:“今晚,真是糟糕透了!”,雷雨交加的黑夜、幽深的别墅、绝美的少女、漆黑的仓库以及恐怖的尸体,这一切显得那么奇特和诡秘,千奈美拿出蜡烛,发现古畑在看书,古畑赞叹千奈美的书真是一部杰作啊。夜深了,两个人都饿了,他们来到储藏室,女主不假思索拿出鸡蛋想做蛋羹汤。细心的古畑怀疑到:你一个多月没来过了,那么这里的鸡蛋真的还能吃吗?
回到别墅,古畑对千奈美说道:“请原谅我得说出事实的真相,三天前,是千奈美小姐和畑野先生一起来到的别墅吧?”,“我上次来这里是一个月前的事情了,”,千奈美面无表情,“你没有说实话。请问你是怎么知道冰箱里的鸡蛋还是新鲜的?你最近一次来这里不是一个月前,也就是说你是三天前把鸡蛋放进冰箱的。在我的想象中,不知世间冷暖的美女漫画家和花花公子般的出版社编辑,你们之间发生了什么我不清楚,但其中一人终于发现对方只是逢场作戏,于是进行了残酷的报复。”,“古畑先生,这真是一个俗套的故事呢”,千奈美笑了笑,“凶手为什么会打破死者的头呢?如果让别人以为这是一起意外的话,凶手是绝不会这么做的。对吗?可是被害人却被打伤了,这一点很矛盾。于是我仔细考虑了一下,把谋杀伪装成意外的人和故意留下他杀证据的人会不会不是同一人呢?”,
“难道凶手有两个人吗?”,“凶手只有一个,我们其实忽略了非常关键的一点,就是这张白纸,死者确实有留下了线索,只是我们还没有发现罢了。线索就留存在这张纸上。我们换位思考一下,试想畑野先生当时的感受,他无论如何都想留下凶手的名字,但不管他如何想办法写下凶手的名字,一旦要是被凶手先发现的话,就会被销毁,他非常明确这一点,最先发现尸体的一定是凶手,因为凶手把他关在仓库里的,于是呢,他冥思苦想,先拿起一张纸,再把笔帽摘下,而就是什么都没有写,你懂了吗?在能够写下凶手名字的情况下,他什么都没有写,这就是他所留下的线索。他想传达的意思就是‘无论我写什么都是没用的’”。

“那他头上的伤呢?怎么解释?“

“这也是畑野先生留下的线索之一。因为他不管怎么想办法留下线索,如果万一案子被当作意外处理了,那么一切都没有意义了。所以他无论如何都要留下这是他杀的证据,因此,他自己给自己的脑袋来了一下。”
是的,一张什么都没有写的白纸就是亡者留下的线索。这张白纸在默然无声地诉说:杀死我的凶手,就是最先发现我尸体的人。
这个故事本身并不复杂,由爱生恨的狗血谋杀案,但是故事的核心却是一张白纸,亡者留下的唯一线索,这个线索被古畑发现了,亡者不是不想说出凶手的名字,而是不能说,这里古畑运用的实际上就是基于逆向思维的递归逻辑。

那么,从代码层面上来看,递归可以帮我们解决什么问题呢?我们以高斯求和为例子,所谓高斯求和,即在一个阈值范围内,将所有的整数相加求和的算术题,如果使用迭代逻辑:

def sum_number(n):  
    total = 0  
    for i in range(1, n+1):  
        total += i  
    return total

调用方法:

print(sum_number(5))  
  
liuyue:mytornado liuyue$ python3 "/Users/liuyue/wodfan/work/mytornado/excel_test.py"  
1  
3  
6  
10  
15  
15  
liuyue:mytornado liuyue$

可以看到,迭代思想的本质是递增遍历,按照顺序将元素一个一个的累加,并不难理解,接着我们来试试递归的解法:

def sum_number(n):  
    if n <= 0:  
        return 0  
    return n+sum_number(n-1)

可以看到,当我们使用递归设计程序的时候,我们从最终结果入手,即要想求得sum\_number(5),电脑会把这个计算拆解为求得sum\_number(4)的运算,以及sum\_number(4)加上5的运算。以此类推,直到拆解为sum\_number(1)的运算,就触发终止条件,也就是if结构中n<=0时,返回一个具体的数0。尽管整个递归过程很复杂,但在编写程序时,我们只需关注初始条件、终止条件及衔接,而无须关注具体的每一步。

递归思维是自顶而下的,我们做事的时候可以先从整体上考虑。先明确需要达到的大目标,而不是一开始就在细节上较真,这其实也是系统论的思想。很多初入职的程序员,在没有清楚项目整体功能架构的情况下,就急于写代码,最终往往导致多次返工,事倍功半,不过使用Python设计递归程序需要注意栈溢出的问题,如果递归深度超出1000层就会报错,所以需要单独设置递归深度:

import sys  
#更改递归深度为1百万  
sys.setrecursionlimit(1000000)

搞清楚递归的简单思路,让我们来试一试进阶的操作:尾递归。尾递归相对传统递归,其是一种特例。在尾递归中,先执行某部分的计算,然后开始调用递归,所以你可以得到当前的计算结果,而这个结果也将作为参数传入下一次递归。这也就是说函数调用出现在调用者函数的尾部,因为是尾部,所以其有一个优越于传统递归之处在于无需去保存任何局部变量,从内存消耗上,实现节约特性:

def tail_sum(n,result=0):  
     if n==0:  
         return result  
     else:  
         return tail_sum(n-1, result+n)

传统递归的解题步骤:

5+sum_number(4)  
5+(4+sum_number(3))  
5+(4+(3+sum_number(2)))  
5+(4+(3+(2+sum_number(1))))  
5+(4+(3+(2+1)))  
15

每一次递归,程序会将计算结果存储在内存中,在递归过程中做累加,直到走向递归出口,尾递归则是通过传参将累加结果进行递归传递:

tail_sum(5,0)  
tail_sum(4,5)  
tail_sum(3,9)  
tail_sum(2,12)  
tail_sum(1,14)  
tail_sum(0,15)

如果递归深度非常大的情况下,就可以大量节约内存成本。

相关视频攻略,请移步:

https://www.bilibili.com/vide...

综上,熟练运用递归需要注意以下三个特点:

1.问题本身可以拆分成更为简单的子问题,而子问题可以通过相同的方法解决。

2.解题需要提前考虑程序出口,否则会掉进递归死循环的陷阱。

3.递归并不是非常高效的算法,大数量级的问题需要尾递归的参与。

结语:掌握递归逆向思维的人,在解决一些棘手的问题时,往往能够另辟蹊径,善于运用资源,找到解决问题的巧妙办法,就像文章开篇的古畑刑警一样。当我们面对生活中、社会上的种种问题,是否能够想象出鲁迅先生那句“从来如此,便对么”的呐喊。“从来如此”或许对,或许不对。但是作为当代人,自省吾身,与君共勉。

原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_186

查看原文

赞 0 收藏 0 评论 0

刘悦的技术博客 收藏了文章 · 2月8日

前端性能优化 24 条建议(2020)

性能优化是把双刃剑,有好的一面也有坏的一面。好的一面就是能提升网站性能,坏的一面就是配置麻烦,或者要遵守的规则太多。并且某些性能优化规则并不适用所有场景,需要谨慎使用,请读者带着批判性的眼光来阅读本文。

本文相关的优化建议的引用资料出处均会在建议后面给出,或者放在文末。

1. 减少 HTTP 请求

一个完整的 HTTP 请求需要经历 DNS 查找,TCP 握手,浏览器发出 HTTP 请求,服务器接收请求,服务器处理请求并发回响应,浏览器接收响应等过程。接下来看一个具体的例子帮助理解 HTTP :

在这里插入图片描述

这是一个 HTTP 请求,请求的文件大小为 28.4KB。

名词解释:

  • Queueing: 在请求队列中的时间。
  • Stalled: 从TCP 连接建立完成,到真正可以传输数据之间的时间差,此时间包括代理协商时间。
  • Proxy negotiation: 与代理服务器连接进行协商所花费的时间。
  • DNS Lookup: 执行DNS查找所花费的时间,页面上的每个不同的域都需要进行DNS查找。
  • Initial Connection / Connecting: 建立连接所花费的时间,包括TCP握手/重试和协商SSL。
  • SSL: 完成SSL握手所花费的时间。
  • Request sent: 发出网络请求所花费的时间,通常为一毫秒的时间。
  • Waiting(TFFB): TFFB 是发出页面请求到接收到应答数据第一个字节的时间。
  • Content Download: 接收响应数据所花费的时间。

从这个例子可以看出,真正下载数据的时间占比为 13.05 / 204.16 = 6.39%,文件越小,这个比例越小,文件越大,比例就越高。这就是为什么要建议将多个小文件合并为一个大文件,从而减少 HTTP 请求次数的原因。

参考资料:

2. 使用 HTTP2

HTTP2 相比 HTTP1.1 有如下几个优点:

解析速度快

服务器解析 HTTP1.1 的请求时,必须不断地读入字节,直到遇到分隔符 CRLF 为止。而解析 HTTP2 的请求就不用这么麻烦,因为 HTTP2 是基于帧的协议,每个帧都有表示帧长度的字段。

多路复用

HTTP1.1 如果要同时发起多个请求,就得建立多个 TCP 连接,因为一个 TCP 连接同时只能处理一个 HTTP1.1 的请求。

在 HTTP2 上,多个请求可以共用一个 TCP 连接,这称为多路复用。同一个请求和响应用一个流来表示,并有唯一的流 ID 来标识。
多个请求和响应在 TCP 连接中可以乱序发送,到达目的地后再通过流 ID 重新组建。

首部压缩

HTTP2 提供了首部压缩功能。

例如有如下两个请求:

:authority: unpkg.zhimg.com
:method: GET
:path: /za-js-sdk@2.16.0/dist/zap.js
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
:authority: zz.bdstatic.com
:method: GET
:path: /linksubmit/push.js
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36

从上面两个请求可以看出来,有很多数据都是重复的。如果可以把相同的首部存储起来,仅发送它们之间不同的部分,就可以节省不少的流量,加快请求的时间。

HTTP/2 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送。

下面再来看一个简化的例子,假设客户端按顺序发送如下请求首部:

Header1:foo
Header2:bar
Header3:bat

当客户端发送请求时,它会根据首部值创建一张表:

索引首部名称
62Header1foo
63Header2bar
64Header3bat

如果服务器收到了请求,它会照样创建一张表。
当客户端发送下一个请求的时候,如果首部相同,它可以直接发送这样的首部块:

62 63 64

服务器会查找先前建立的表格,并把这些数字还原成索引对应的完整首部。

优先级

HTTP2 可以对比较紧急的请求设置一个较高的优先级,服务器在收到这样的请求后,可以优先处理。

流量控制

由于一个 TCP 连接流量带宽(根据客户端到服务器的网络带宽而定)是固定的,当有多个请求并发时,一个请求占的流量多,另一个请求占的流量就会少。流量控制可以对不同的流的流量进行精确控制。

服务器推送

HTTP2 新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。

例如当浏览器请求一个网站时,除了返回 HTML 页面外,服务器还可以根据 HTML 页面中的资源的 URL,来提前推送资源。

现在有很多网站已经开始使用 HTTP2 了,例如知乎:

在这里插入图片描述

其中 h2 是指 HTTP2 协议,http/1.1 则是指 HTTP1.1 协议。

参考资料:

3. 使用服务端渲染

客户端渲染: 获取 HTML 文件,根据需要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。

服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML。

  • 优点:首屏渲染快,SEO 好。
  • 缺点:配置麻烦,增加了服务器的计算压力。

下面我用 Vue SSR 做示例,简单的描述一下 SSR 过程。

客户端渲染过程

  1. 访问客户端渲染的网站。
  2. 服务器返回一个包含了引入资源语句和 <div id="app"></div> 的 HTML 文件。
  3. 客户端通过 HTTP 向服务器请求资源,当必要的资源都加载完毕后,执行 new Vue() 开始实例化并渲染页面。

服务端渲染过程

  1. 访问服务端渲染的网站。
  2. 服务器会查看当前路由组件需要哪些资源文件,然后将这些文件的内容填充到 HTML 文件。如果有 ajax 请求,就会执行它进行数据预取并填充到 HTML 文件里,最后返回这个 HTML 页面。
  3. 当客户端接收到这个 HTML 页面时,可以马上就开始渲染页面。与此同时,页面也会加载资源,当必要的资源都加载完毕后,开始执行 new Vue() 开始实例化并接管页面。

从上述两个过程中可以看出,区别就在于第二步。客户端渲染的网站会直接返回 HTML 文件,而服务端渲染的网站则会渲染完页面再返回这个 HTML 文件。

这样做的好处是什么?是更快的内容到达时间 (time-to-content)

假设你的网站需要加载完 abcd 四个文件才能渲染完毕。并且每个文件大小为 1 M。

这样一算:客户端渲染的网站需要加载 4 个文件和 HTML 文件才能完成首页渲染,总计大小为 4M(忽略 HTML 文件大小)。而服务端渲染的网站只需要加载一个渲染完毕的 HTML 文件就能完成首页渲染,总计大小为已经渲染完毕的 HTML 文件(这种文件不会太大,一般为几百K,我的个人博客网站(SSR)加载的 HTML 文件为 400K)。这就是服务端渲染更快的原因

参考资料:

4. 静态资源使用 CDN

内容分发网络(CDN)是一组分布在多个不同地理位置的 Web 服务器。我们都知道,当服务器离用户越远时,延迟越高。CDN 就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。

CDN 原理

当用户访问一个网站时,如果没有 CDN,过程是这样的:

  1. 浏览器要将域名解析为 IP 地址,所以需要向本地 DNS 发出请求。
  2. 本地 DNS 依次向根服务器、顶级域名服务器、权限服务器发出请求,得到网站服务器的 IP 地址。
  3. 本地 DNS 将 IP 地址发回给浏览器,浏览器向网站服务器 IP 地址发出请求并得到资源。

如果用户访问的网站部署了 CDN,过程是这样的:

  1. 浏览器要将域名解析为 IP 地址,所以需要向本地 DNS 发出请求。
  2. 本地 DNS 依次向根服务器、顶级域名服务器、权限服务器发出请求,得到全局负载均衡系统(GSLB)的 IP 地址。
  3. 本地 DNS 再向 GSLB 发出请求,GSLB 的主要功能是根据本地 DNS 的 IP 地址判断用户的位置,筛选出距离用户较近的本地负载均衡系统(SLB),并将该 SLB 的 IP 地址作为结果返回给本地 DNS。
  4. 本地 DNS 将 SLB 的 IP 地址发回给浏览器,浏览器向 SLB 发出请求。
  5. SLB 根据浏览器请求的资源和地址,选出最优的缓存服务器发回给浏览器。
  6. 浏览器再根据 SLB 发回的地址重定向到缓存服务器。
  7. 如果缓存服务器有浏览器需要的资源,就将资源发回给浏览器。如果没有,就向源服务器请求资源,再发给浏览器并缓存在本地。

参考资料:

5. 将 CSS 放在文件头部,JavaScript 文件放在底部

所有放在 head 标签里的 CSS 和 JS 文件都会堵塞渲染。如果这些 CSS 和 JS 需要加载和解析很久的话,那么页面就空白了。所以 JS 文件要放在底部,等 HTML 解析完了再加载 JS 文件。

那为什么 CSS 文件还要放在头部呢?

因为先加载 HTML 再加载 CSS,会让用户第一时间看到的页面是没有样式的、“丑陋”的,为了避免这种情况发生,就要将 CSS 文件放在头部了。

另外,JS 文件也不是不可以放在头部,只要给 script 标签加上 defer 属性就可以了,异步下载,延迟执行。

6. 使用字体图标 iconfont 代替图片图标

字体图标就是将图标制作成一个字体,使用时就跟字体一样,可以设置属性,例如 font-size、color 等等,非常方便。并且字体图标是矢量图,不会失真。还有一个优点是生成的文件特别小。

压缩字体文件

使用 fontmin-webpack 插件对字体文件进行压缩(感谢前端小伟提供)。

参考资料:

7. 善用缓存,不重复加载相同的资源

为了避免用户每次访问网站都得请求文件,我们可以通过添加 Expires 或 max-age 来控制这一行为。Expires 设置了一个时间,只要在这个时间之前,浏览器都不会请求文件,而是直接使用缓存。而 max-age 是一个相对时间,建议使用 max-age 代替 Expires 。

不过这样会产生一个问题,当文件更新了怎么办?怎么通知浏览器重新请求文件?

可以通过更新页面中引用的资源链接地址,让浏览器主动放弃缓存,加载新资源。

具体做法是把资源地址 URL 的修改与文件内容关联起来,也就是说,只有文件内容变化,才会导致相应 URL 的变更,从而实现文件级别的精确缓存控制。什么东西与文件内容相关呢?我们会很自然的联想到利用数据摘要要算法对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种可以精确到单个文件粒度的缓存控制依据了。

参考资料:

8. 压缩文件

压缩文件可以减少文件下载时间,让用户体验性更好。

得益于 webpack 和 node 的发展,现在压缩文件已经非常方便了。

在 webpack 可以使用如下插件进行压缩:

  • JavaScript:UglifyPlugin
  • CSS :MiniCssExtractPlugin
  • HTML:HtmlWebpackPlugin

其实,我们还可以做得更好。那就是使用 gzip 压缩。可以通过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识来开启这一功能。当然,服务器也得支持这一功能。

gzip 是目前最流行和最有效的压缩方法。举个例子,我用 Vue 开发的项目构建后生成的 app.js 文件大小为 1.4MB,使用 gzip 压缩后只有 573KB,体积减少了将近 60%。

附上 webpack 和 node 配置 gzip 的使用方法。

下载插件

npm install compression-webpack-plugin --save-dev
npm install compression

webpack 配置

const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
  plugins: [new CompressionPlugin()],
}

node 配置

const compression = require('compression')
// 在其他中间件前使用
app.use(compression())

9. 图片优化

(1). 图片延迟加载

在页面中,先不给图片设置路径,只有当图片出现在浏览器的可视区域时,才去加载真正的图片,这就是延迟加载。对于图片很多的网站来说,一次性加载全部图片,会对用户体验造成很大的影响,所以需要使用图片延迟加载。

首先可以将图片这样设置,在页面不可见时图片不会加载:

<img data-data-original="https://avatars0.githubusercontent.com/u/22117876?s=460&u=7bd8f32788df6988833da6bd155c3cfbebc68006&v=4">

等页面可见时,使用 JS 加载图片:

const img = document.querySelector('img')
img.src = img.dataset.src

这样图片就加载出来了,完整的代码可以看一下参考资料。

参考资料:

(2). 响应式图片

响应式图片的优点是浏览器能够根据屏幕大小自动加载合适的图片。

通过 picture 实现

<picture>
    <source srcset="banner_w1000.jpg" media="(min-width: 801px)">
    <source srcset="banner_w800.jpg" media="(max-width: 800px)">
    <img data-original="banner_w800.jpg" alt="">
</picture>

通过 @media 实现

@media (min-width: 769px) {
    .bg {
        background-image: url(bg1080.jpg);
    }
}
@media (max-width: 768px) {
    .bg {
        background-image: url(bg768.jpg);
    }
}

(3). 调整图片大小

例如,你有一个 1920 * 1080 大小的图片,用缩略图的方式展示给用户,并且当用户鼠标悬停在上面时才展示全图。如果用户从未真正将鼠标悬停在缩略图上,则浪费了下载图片的时间。

所以,我们可以用两张图片来实行优化。一开始,只加载缩略图,当用户悬停在图片上时,才加载大图。还有一种办法,即对大图进行延迟加载,在所有元素都加载完成后手动更改大图的 src 进行下载。

(4). 降低图片质量

例如 JPG 格式的图片,100% 的质量和 90% 质量的通常看不出来区别,尤其是用来当背景图的时候。我经常用 PS 切背景图时, 将图片切成 JPG 格式,并且将它压缩到 60% 的质量,基本上看不出来区别。

压缩方法有两种,一是通过 webpack 插件 image-webpack-loader,二是通过在线网站进行压缩。

以下附上 webpack 插件 image-webpack-loader 的用法。

npm i -D image-webpack-loader

webpack 配置

{
  test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
  use:[
    {
    loader: 'url-loader',
    options: {
      limit: 10000, /* 图片大小小于1000字节限制时会自动转成 base64 码引用*/
      name: utils.assetsPath('img/[name].[hash:7].[ext]')
      }
    },
    /*对图片进行压缩*/
    {
      loader: 'image-webpack-loader',
      options: {
        bypassOnDebug: true,
      }
    }
  ]
}

(5). 尽可能利用 CSS3 效果代替图片

有很多图片使用 CSS 效果(渐变、阴影等)就能画出来,这种情况选择 CSS3 效果更好。因为代码大小通常是图片大小的几分之一甚至几十分之一。

参考资料:

(6). 使用 webp 格式的图片

WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。

参考资料:

10. 通过 webpack 按需加载代码,提取第三库代码,减少 ES6 转为 ES5 的冗余代码

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

根据文件内容生成文件名,结合 import 动态引入组件实现按需加载

通过配置 output 的 filename 属性可以实现这个需求。filename 属性的值选项中有一个 [contenthash],它将根据文件内容创建出唯一 hash。当文件内容发生变化时,[contenthash] 也会发生变化。

output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js',
    path: path.resolve(__dirname, '../dist'),
},

提取第三方库

由于引入的第三方库一般都比较稳定,不会经常改变。所以将它们单独提取出来,作为长期缓存是一个更好的选择。
这里需要使用 webpack4 的 splitChunk 插件 cacheGroups 选项。

optimization: {
      runtimeChunk: {
        name: 'manifest' // 将 webpack 的 runtime 代码拆分为一个单独的 chunk。
    },
    splitChunks: {
        cacheGroups: {
            vendor: {
                name: 'chunk-vendors',
                test: /[\\/]node_modules[\\/]/,
                priority: -10,
                chunks: 'initial'
            },
            common: {
                name: 'chunk-common',
                minChunks: 2,
                priority: -20,
                chunks: 'initial',
                reuseExistingChunk: true
            }
        },
    }
},
  • test: 用于控制哪些模块被这个缓存组匹配到。原封不动传递出去的话,它默认会选择所有的模块。可以传递的值类型:RegExp、String和Function;
  • priority:表示抽取权重,数字越大表示优先级越高。因为一个 module 可能会满足多个 cacheGroups 的条件,那么抽取到哪个就由权重最高的说了算;
  • reuseExistingChunk:表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。
  • minChunks(默认是1):在分割之前,这个代码块最小应该被引用的次数(译注:保证代码块复用性,默认配置的策略是不需要多次引用也可以被分割)
  • chunks (默认是async) :initial、async和all
  • name(打包的chunks的名字):字符串或者函数(函数可以根据条件自定义名字)

减少 ES6 转为 ES5 的冗余代码

Babel 转化后的代码想要实现和原来代码一样的功能需要借助一些帮助函数,比如:

class Person {}

会被转换为:

"use strict";

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var Person = function Person() {
  _classCallCheck(this, Person);
};

这里 _classCallCheck 就是一个 helper 函数,如果在很多文件里都声明了类,那么就会产生很多个这样的 helper 函数。

这里的 @babel/runtime 包就声明了所有需要用到的帮助函数,而 @babel/plugin-transform-runtime 的作用就是将所有需要 helper 函数的文件,从 @babel/runtime包 引进来:

"use strict";

var _classCallCheck2 = require("@babel/runtime/helpers/classCallCheck");

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

var Person = function Person() {
  (0, _classCallCheck3.default)(this, Person);
};

这里就没有再编译出 helper 函数 classCallCheck 了,而是直接引用了 @babel/runtime 中的 helpers/classCallCheck

安装

npm i -D @babel/plugin-transform-runtime @babel/runtime

使用
.babelrc 文件中

"plugins": [
        "@babel/plugin-transform-runtime"
]

参考资料:

11. 减少重绘重排

浏览器渲染过程

  1. 解析HTML生成DOM树。
  2. 解析CSS生成CSSOM规则树。
  3. 将DOM树与CSSOM规则树合并在一起生成渲染树。
  4. 遍历渲染树开始布局,计算每个节点的位置大小信息。
  5. 将渲染树每个节点绘制到屏幕。

在这里插入图片描述

重排

当改变 DOM 元素位置或大小时,会导致浏览器重新生成渲染树,这个过程叫重排。

重绘

当重新生成渲染树后,就要将渲染树每个节点绘制到屏幕,这个过程叫重绘。不是所有的动作都会导致重排,例如改变字体颜色,只会导致重绘。记住,重排会导致重绘,重绘不会导致重排 。

重排和重绘这两个操作都是非常昂贵的,因为 JavaScript 引擎线程与 GUI 渲染线程是互斥,它们同时只能一个在工作。

什么操作会导致重排?

  • 添加或删除可见的 DOM 元素
  • 元素位置改变
  • 元素尺寸改变
  • 内容改变
  • 浏览器窗口尺寸改变

如何减少重排重绘?

  • 用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。
  • 如果要对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement),都能很好的实现这个方案。

12. 使用事件委托

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托可以节省内存。

<ul>
  <li>苹果</li>
  <li>香蕉</li>
  <li>凤梨</li>
</ul>

// good
document.querySelector('ul').onclick = (event) => {
  const target = event.target
  if (target.nodeName === 'LI') {
    console.log(target.innerHTML)
  }
}

// bad
document.querySelectorAll('li').forEach((e) => {
  e.onclick = function() {
    console.log(this.innerHTML)
  }
}) 

13. 注意程序的局部性

一个编写良好的计算机程序常常具有良好的局部性,它们倾向于引用最近引用过的数据项附近的数据项,或者最近引用过的数据项本身,这种倾向性,被称为局部性原理。有良好局部性的程序比局部性差的程序运行得更快。

局部性通常有两种不同的形式:

  • 时间局部性:在一个具有良好时间局部性的程序中,被引用过一次的内存位置很可能在不远的将来被多次引用。
  • 空间局部性 :在一个具有良好空间局部性的程序中,如果一个内存位置被引用了一次,那么程序很可能在不远的将来引用附近的一个内存位置。

时间局部性示例

function sum(arry) {
    let i, sum = 0
    let len = arry.length

    for (i = 0; i < len; i++) {
        sum += arry[i]
    }

    return sum
}

在这个例子中,变量sum在每次循环迭代中被引用一次,因此,对于sum来说,具有良好的时间局部性

空间局部性示例

具有良好空间局部性的程序

// 二维数组 
function sum1(arry, rows, cols) {
    let i, j, sum = 0

    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            sum += arry[i][j]
        }
    }
    return sum
}

空间局部性差的程序

// 二维数组 
function sum2(arry, rows, cols) {
    let i, j, sum = 0

    for (j = 0; j < cols; j++) {
        for (i = 0; i < rows; i++) {
            sum += arry[i][j]
        }
    }
    return sum
}

看一下上面的两个空间局部性示例,像示例中从每行开始按顺序访问数组每个元素的方式,称为具有步长为1的引用模式。
如果在数组中,每隔k个元素进行访问,就称为步长为k的引用模式。
一般而言,随着步长的增加,空间局部性下降。

这两个例子有什么区别?区别在于第一个示例是按行扫描数组,每扫描完一行再去扫下一行;第二个示例是按列来扫描数组,扫完一行中的一个元素,马上就去扫下一行中的同一列元素。

数组在内存中是按照行顺序来存放的,结果就是逐行扫描数组的示例得到了步长为 1 引用模式,具有良好的空间局部性;而另一个示例步长为 rows,空间局部性极差。

性能测试

运行环境:

  • cpu: i5-7400
  • 浏览器: chrome 70.0.3538.110

对一个长度为9000的二维数组(子数组长度也为9000)进行10次空间局部性测试,时间(毫秒)取平均值,结果如下:

所用示例为上述两个空间局部性示例

步长为 1步长为 9000
1242316

从以上测试结果来看,步长为 1 的数组执行时间比步长为 9000 的数组快了一个数量级。

总结:

  • 重复引用相同变量的程序具有良好的时间局部性
  • 对于具有步长为 k 的引用模式的程序,步长越小,空间局部性越好;而在内存中以大步长跳来跳去的程序空间局部性会很差

参考资料:

14. if-else 对比 switch

当判断条件数量越来越多时,越倾向于使用 switch 而不是 if-else。

if (color == 'blue') {

} else if (color == 'yellow') {

} else if (color == 'white') {

} else if (color == 'black') {

} else if (color == 'green') {

} else if (color == 'orange') {

} else if (color == 'pink') {

}

switch (color) {
    case 'blue':

        break
    case 'yellow':

        break
    case 'white':

        break
    case 'black':

        break
    case 'green':

        break
    case 'orange':

        break
    case 'pink':

        break
}

像以上这种情况,使用 switch 是最好的。假设 color 的值为 pink,则 if-else 语句要进行 7 次判断,switch 只需要进行一次判断。 从可读性来说,switch 语句也更好。

从使用时机来说,当条件值大于两个的时候,使用 switch 更好。不过 if-else 也有 switch 无法做到的事情,例如有多个判断条件的情况下,无法使用 switch。

15. 查找表

当条件语句特别多时,使用 switch 和 if-else 不是最佳的选择,这时不妨试一下查找表。查找表可以使用数组和对象来构建。

switch (index) {
    case '0':
        return result0
    case '1':
        return result1
    case '2':
        return result2
    case '3':
        return result3
    case '4':
        return result4
    case '5':
        return result5
    case '6':
        return result6
    case '7':
        return result7
    case '8':
        return result8
    case '9':
        return result9
    case '10':
        return result10
    case '11':
        return result11
}

可以将这个 switch 语句转换为查找表

const results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,result11]

return results[index]

如果条件语句不是数值而是字符串,可以用对象来建立查找表

const map = {
  red: result0,
  green: result1,
}

return map[color]

16. 避免页面卡顿

60fps 与设备刷新率

目前大多数设备的屏幕刷新率为 60 次/秒。因此,如果在页面中有一个动画或渐变效果,或者用户正在滚动页面,那么浏览器渲染动画或页面的每一帧的速率也需要跟设备屏幕的刷新率保持一致。
其中每个帧的预算时间仅比 16 毫秒多一点 (1 秒/ 60 = 16.66 毫秒)。但实际上,浏览器有整理工作要做,因此您的所有工作需要在 10 毫秒内完成。如果无法符合此预算,帧率将下降,并且内容会在屏幕上抖动。 此现象通常称为卡顿,会对用户体验产生负面影响。

在这里插入图片描述

假如你用 JavaScript 修改了 DOM,并触发样式修改,经历重排重绘最后画到屏幕上。如果这其中任意一项的执行时间过长,都会导致渲染这一帧的时间过长,平均帧率就会下降。假设这一帧花了 50 ms,那么此时的帧率为 1s / 50ms = 20fps,页面看起来就像卡顿了一样。

对于一些长时间运行的 JavaScript,我们可以使用定时器进行切分,延迟执行。

for (let i = 0, len = arry.length; i < len; i++) {
    process(arry[i])
}

假设上面的循环结构由于 process() 复杂度过高或数组元素太多,甚至两者都有,可以尝试一下切分。

const todo = arry.concat()
setTimeout(function() {
    process(todo.shift())
    if (todo.length) {
        setTimeout(arguments.callee, 25)
    } else {
        callback(arry)
    }
}, 25)

如果有兴趣了解更多,可以查看一下高性能JavaScript第 6 章和高效前端:Web高效编程与优化实践第 3 章。

参考资料:

17. 使用 requestAnimationFrame 来实现视觉变化

从第 16 点我们可以知道,大多数设备屏幕刷新率为 60 次/秒,也就是说每一帧的平均时间为 16.66 毫秒。在使用 JavaScript 实现动画效果的时候,最好的情况就是每次代码都是在帧的开头开始执行。而保证 JavaScript 在帧开始时运行的唯一方式是使用 requestAnimationFrame

/**
 * If run as a requestAnimationFrame callback, this
 * will be run at the start of the frame.
 */
function updateScreen(time) {
  // Make visual updates here.
}

requestAnimationFrame(updateScreen);

如果采取 setTimeoutsetInterval 来实现动画的话,回调函数将在帧中的某个时点运行,可能刚好在末尾,而这可能经常会使我们丢失帧,导致卡顿。

在这里插入图片描述

参考资料:

18. 使用 Web Workers

Web Worker 使用其他工作线程从而独立于主线程之外,它可以执行任务而不干扰用户界面。一个 worker 可以将消息发送到创建它的 JavaScript 代码, 通过将消息发送到该代码指定的事件处理程序(反之亦然)。

Web Worker 适用于那些处理纯数据,或者与浏览器 UI 无关的长时间运行脚本。

创建一个新的 worker 很简单,指定一个脚本的 URI 来执行 worker 线程(main.js):

var myWorker = new Worker('worker.js');
// 你可以通过postMessage() 方法和onmessage事件向worker发送消息。
first.onchange = function() {
  myWorker.postMessage([first.value,second.value]);
  console.log('Message posted to worker');
}

second.onchange = function() {
  myWorker.postMessage([first.value,second.value]);
  console.log('Message posted to worker');
}

在 worker 中接收到消息后,我们可以写一个事件处理函数代码作为响应(worker.js):

onmessage = function(e) {
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult);
}

onmessage处理函数在接收到消息后马上执行,代码中消息本身作为事件的data属性进行使用。这里我们简单的对这2个数字作乘法处理并再次使用postMessage()方法,将结果回传给主线程。

回到主线程,我们再次使用onmessage以响应worker回传的消息:

myWorker.onmessage = function(e) {
  result.textContent = e.data;
  console.log('Message received from worker');
}

在这里我们获取消息事件的data,并且将它设置为result的textContent,所以用户可以直接看到运算的结果。

不过在worker内,不能直接操作DOM节点,也不能使用window对象的默认方法和属性。然而你可以使用大量window对象之下的东西,包括WebSockets,IndexedDB以及FireFox OS专用的Data Store API等数据存储机制。

参考资料:

19. 使用位操作

JavaScript 中的数字都使用 IEEE-754 标准以 64 位格式存储。但是在位操作中,数字被转换为有符号的 32 位格式。即使需要转换,位操作也比其他数学运算和布尔操作快得多。

取模

由于偶数的最低位为 0,奇数为 1,所以取模运算可以用位操作来代替。

if (value % 2) {
    // 奇数
} else {
    // 偶数 
}
// 位操作
if (value & 1) {
    // 奇数
} else {
    // 偶数
}
取整
~~10.12 // 10
~~10 // 10
~~'1.5' // 1
~~undefined // 0
~~null // 0
位掩码
const a = 1
const b = 2
const c = 4
const options = a | b | c

通过定义这些选项,可以用按位与操作来判断 a/b/c 是否在 options 中。

// 选项 b 是否在选项中
if (b & options) {
    ...
}

20. 不要覆盖原生方法

无论你的 JavaScript 代码如何优化,都比不上原生方法。因为原生方法是用低级语言写的(C/C++),并且被编译成机器码,成为浏览器的一部分。当原生方法可用时,尽量使用它们,特别是数学运算和 DOM 操作。

21. 降低 CSS 选择器的复杂性

(1). 浏览器读取选择器,遵循的原则是从选择器的右边到左边读取。

看个示例

#block .text p {
    color: red;
}
  1. 查找所有 P 元素。
  2. 查找结果 1 中的元素是否有类名为 text 的父元素
  3. 查找结果 2 中的元素是否有 id 为 block 的父元素

(2). CSS 选择器优先级

内联 > ID选择器 > 类选择器 > 标签选择器

根据以上两个信息可以得出结论。

  1. 选择器越短越好。
  2. 尽量使用高优先级的选择器,例如 ID 和类选择器。
  3. 避免使用通配符 *。

最后要说一句,据我查找的资料所得,CSS 选择器没有优化的必要,因为最慢和慢快的选择器性能差别非常小。

参考资料:

22. 使用 flexbox 而不是较早的布局模型

在早期的 CSS 布局方式中我们能对元素实行绝对定位、相对定位或浮动定位。而现在,我们有了新的布局方式 flexbox,它比起早期的布局方式来说有个优势,那就是性能比较好。

下面的截图显示了在 1300 个框上使用浮动的布局开销:

在这里插入图片描述

然后我们用 flexbox 来重现这个例子:

在这里插入图片描述

现在,对于相同数量的元素和相同的视觉外观,布局的时间要少得多(本例中为分别 3.5 毫秒和 14 毫秒)。

不过 flexbox 兼容性还是有点问题,不是所有浏览器都支持它,所以要谨慎使用。

各浏览器兼容性:

  • Chrome 29+
  • Firefox 28+
  • Internet Explorer 11
  • Opera 17+
  • Safari 6.1+ (prefixed with -webkit-)
  • Android 4.4+
  • iOS 7.1+ (prefixed with -webkit-)

参考资料:

23. 使用 transform 和 opacity 属性更改来实现动画

在 CSS 中,transforms 和 opacity 这两个属性更改不会触发重排与重绘,它们是可以由合成器(composite)单独处理的属性。

在这里插入图片描述

参考资料:

24. 合理使用规则,避免过度优化

性能优化主要分为两类:

  1. 加载时优化
  2. 运行时优化

上述 23 条建议中,属于加载时优化的是前面 10 条建议,属于运行时优化的是后面 13 条建议。通常来说,没有必要 23 条性能优化规则都用上,根据网站用户群体来做针对性的调整是最好的,节省精力,节省时间。

在解决问题之前,得先找出问题,否则无从下手。所以在做性能优化之前,最好先调查一下网站的加载性能和运行性能。

检查加载性能

一个网站加载性能如何主要看白屏时间和首屏时间。

  • 白屏时间:指从输入网址,到页面开始显示内容的时间。
  • 首屏时间:指从输入网址,到页面完全渲染的时间。

将以下脚本放在 </head> 前面就能获取白屏时间。

<script>
    new Date() - performance.timing.navigationStart
</script>

window.onload 事件里执行 new Date() - performance.timing.navigationStart 即可获取首屏时间。

检查运行性能

配合 chrome 的开发者工具,我们可以查看网站在运行时的性能。

打开网站,按 F12 选择 performance,点击左上角的灰色圆点,变成红色就代表开始记录了。这时可以模仿用户使用网站,在使用完毕后,点击 stop,然后你就能看到网站运行期间的性能报告。如果有红色的块,代表有掉帧的情况;如果是绿色,则代表 FPS 很好。performance 的具体使用方法请用搜索引擎搜索一下,毕竟篇幅有限。

通过检查加载和运行性能,相信你对网站性能已经有了大概了解。所以这时候要做的事情,就是使用上述 23 条建议尽情地去优化你的网站,加油!

参考资料:

其他参考资料

更多文章,欢迎关注

查看原文

刘悦的技术博客 关注了用户 · 2月8日

谭光志 @woai3c

公众号:前端编程技术分享

知乎:https://www.zhihu.com/people/...

github: https://github.com/woai3c

关注 10298

认证与成就

  • 获得 10 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-03-28
个人主页被 1.8k 人浏览