Normal view

There are new articles available, click to refresh the page.
Before yesterdayMain stream

引发 LTO 优化后续错误的一种可能及解决方法

31 May 2024 at 14:02

最近开发 C++ 的程序在链接阶段发生了一个 LTO 相关的错误。链接时优化(Link Time Optimization, LTO) 是一种编译优化技术,旨在通过在链接阶段执行全程序优化来提升代码性能和减少二进制文件大小。其主要原理是在链接阶段而不是在编译阶段进行更高级的优化,允许跨文件的全局优化。LTO 的基本原理和过程是:

  • 中间表示:在编译阶段,编译器将每个源文件编译为一种中间表示(如 LLVM bitcode),而不是直接生成目标代码。
  • 全局优化:在链接阶段,链接器将所有中间表示文件合并,生成一个完整的程序表示。此时,优化器可以进行跨文件的全局优化,如函数内联、去除冗余代码和跨模块优化。
  • 生成目标文件:优化完成后,链接器生成最终的可执行文件或库。

我遇到的错误信息中的管件一句是:

1
attempt to add bitcode file after LTO (htons)

可以看到错误信息提示我们链接系统试图在 LTO 优化之后添加 bitcode。并且在后面的括号中链接器提到了一个符号 htons,这是来自 libc 库的一个符号。基于的 https://github.com/emscripten-core/emscripten/issues/16836 这个链接中的启发,我发现了一个 LTO 优化可能面临的普遍性问题。这个问题是 LTO 会尝试移除一些它认为不需要的符号从而减少编译出来的程序的体积,但是这个判断可能会不准确。它可能将一些实际会被使用的符号删除。为了解决这个问题,我们可以为链接器指定链接输入,强制保留指定的符号以避免错误的移除。针对我遇到的情况,为链接器加上 -Wl,-u,htons 的配置即可解决问题。

  • -Wl: 这个标志告诉编译器将接下来的选项传递给链接器(ld)。-Wl,option 是传递给链接器的标准方法。
  • -u,symbol: 这个选项告诉链接器强制包含指定的符号 symbol。在这个情况下,-u,htons 和 -u,htonl 告诉链接器强制包含 htons 和 htonl 函数

九个月的努力

28 February 2017 at 15:10

在过去九个月里,我一直在忙着做个小项目。经常关注我动态的朋友们应该已经知道我做的是一个社交 App,但至于它是什么样的,我一直有意保密,所以真正了解细节的人屈指可数。不过随着第一个版本的开发工作即将完成,现在是时候揭开它的神秘面纱了。

在这个信息大爆炸的时代,想在网上和好友分享点东西已经有了太多选择。各种社交网络看起来似乎完全不同,有着不同的功能特色、满足着不同的需求,但实际上它们都有着一个共同点。下图右侧是常见的几款社交类 App 的界面,左侧是我将它们抽象化之后的样子,不难发现其实它们本质上其实都是一样的:上面是你的头像和昵称,然后是文字内容,或许还会有张图片。这些元素组合起来就是一条信息,把很多条这样的信息从上到下叠在一起就成为了我们熟悉的信息流。

界面抽象化之后,其实都是一样的

信息流几乎是所有社交类 UGC 产品的核心。近十年以来,互联网已经发生了翻天覆地的变化,信息流却没有过多少根本性的改变。这种信息流的优势在于可以让你在非常短的时间内快速浏览大量碎片化的信息,起初感觉挺爽的,但现在越来越多人逐渐意识到信息太过碎片化并不是什么好事。至于碎片化究竟是好还是坏,我在这里就不作评判了,不过随着 Twitter 和微博相继放开 140 字符的限制,相信答案还是显而易见的。

限制放开之后,尴尬的情况就出现了:传统的信息流并不适应较长的内容。因为它是一维的,内容只能朝着一个方向扩展,所以内容太长就会打破整体的浏览节奏。对于这个问题,通常的解决方法是在信息流里仅显示少量预览内容,点击之后才展开或跳转到新页面查看全文。当然还有更简单粗暴的解决方法:把文字都拼到长长的渣画质图片里发布,也就是我们熟悉的长微博。这些处理方法虽然能解决问题,却一点也不优雅。在我看来,要完美地解决这个问题,唯有完全彻底地重新设计一个信息流。而这,正是我在过去九个月时间里所做的。

传统信息流是一维的,只能朝一个方向扩展

我发现,信息流的长短是由两个维度决定的,分别是时间轴和内容轴。传统信息流将这两个维度压缩进一维的空间里,扩展能力自然会受到限制。毕竟在它出现的时代,我们与用户界面交互时所用的主要设备还是鼠标,而具体到翻阅网页时,则是鼠标的滚轮——只能上下滚动。虽然在移动触屏设备成为主流的今天,这个问题早已不复存在,无论是上下滑动还是左右滑动都是习以为常的操作,但信息流的设计却依然继承着 PC 时代遗留下来的传统操作习惯,未能充分利用触摸屏幕的优势。

现在,是时候改变一下了。

我发明了一种全新的二维信息流,它可以往水平和垂直两个维度扩展,横向是时间,纵向是内容。这种经过“升维”的设计完全突破了内容长度的限制。用户的发布内容显示在长度可变的卡片里,左右滑动浏览不同的卡片,上下滑动查看单张卡片的内容。

二维信息流可朝两个方向扩展,彻底摆脱单条内容的长度限制

既然有理论上可以承载无限长度内容的信息流,如何充分利用它?这就要说到我的第二大创新——模块化内容。也就是说,卡片里的所有内容都是由模块组成的,每种模块都有特定的功能,比如想发一段文字,就用文字模块;要分享一组照片,则可以用图片模块。我目前已经开发了文字、图片、视频、语音和位置模块,还有更多模块正在设计和开发中。

这些模块可以任意添加、排序及组合。每个模块也有许多可自定义的细节,比如文字的字体、字号和对齐方式,或者图片的形状和排列方式等等;当你分享所在位置时,甚至连地图的缩放比例都可以调节。

不过,要是有些时候懒得调整那么多细节,只想简单快速地分享些东西呢?没问题。我开发了一个自动排版引擎,它会分析每张卡片的内容与模块组合情况,动态计算与调整各个模块的样式、尺寸和间距等几十项参数。无论你输入的是短短几个字,还是包含许多模块的复杂内容,都能自动为你生成一张好看的卡片。

智能的排版渲染配合高度个性化的模块化设计彻底摆脱了固定格式的束缚,让你充分发挥自己的想象力,分享不拘一格。“变化”是这个 App 的核心,所以我给它起的名字就是:Vary。

Vary 卡片效果展示

Vary 是一个定位于在熟识好友间分享生活的社交网络,重点是私密和清净。在这里,没有网站链接,没有广告和软文,没有代购和营销,没有无谓的鸡汤,也没有繁重的人际关系。只是在喧闹纷乱的网络世界里,一个可以安安静静地与你在乎的、真正关心你的人分享和记录自己生活的地方。

如今的互联网世界,已经有了太多助你结识陌生人的社交产品,但专为私密好友间交流的却寥寥可数。在我看来,这很大程度上是因为普遍观点认为这种产品难以大量聚拢用户和维持活跃度,若从商业角度出发,无论是大型互联网公司还是创业公司都不太会优先考虑这种产品。然而 Vary 是我出于兴趣爱好而制作的个人作品,商业利益并不是我的首要考虑,因此在这里你会看到许多与主流互联网产品“反其道而行”的设计:使用纯数字帐号体系,只有知道这个帐号的人才能向你发送好友请求,除此之外无法通过其他途径搜索到你;只有你同意与对方成为好友后,对方才能看到你发布的卡片;不会通过匹配手机或邮箱向你推荐那些你可能认识(但永远不想加为好友)的人;你发出的评论和点赞也只有接收者可见。总而言之,一切尽在你的掌控之中。

此外,在 Vary 上可添加的好友数量是有限的,这个上限数量会随着你的使用情况逐渐增加。如果你想让更多人看到你创作的卡片,可以用内置的分享功能将卡片分享到微博、微信或者其他社交网络,也可以生成分享链接直接发送给别人。这个分享功能是开发过程中的一大技术难题,我优化与调整了无数次,才让卡片内容在不同环境里的显示都能够尽量接近原生 App 里的效果。

Vary 卡片外部分享效果

我是一个极简主义者,相信这点已在 Vary 的设计上体现得淋漓尽致。通常简化设计的思维方式是做减法,即思考现有的东西里哪些是可以去除的;而我则喜欢从零开始做加法:彻底放下成见,从根本上思考每一个元素存在的必要性。这种思维方式在 Vary 的界面、交互和功能上都有所体现,比如注册帐号时,你需要填写的东西精简到了极致:不需要任何个人信息,也不用验证激活,只要设置一个登录密码——没错,只填个密码就能完成注册并开始使用,比第三方帐号登录还方便。

同时我也在不断研究如何通过技术手段来引导交互层面的创新,例如 Vary 的 iOS App 可以检测手指接触屏幕的面积,并以此来调整卡片的滚动速度:用指尖滑动时,一次只会滚动一张卡片;整个手指贴在屏幕上滑动时,则会根据你滑动速度的惯性来连续滚动多张卡片。这听起来也许有些玄乎,却非常容易上手,也很实用。

界面设计方面,自然是我代表性的简约风格,并采用以高冷暗色系为主的纯黑白设计,除极少数 UI 元素外再无任何颜色,层次感由灰度、透明度与模糊效果来体现。我一向注重细节,Vary 的界面细节更是以艺术品的标准来要求的,每一个图标、每一帧动画都由我亲手绘制并在程序中实现,处处细节都经过反复打磨,尽可能追求完美。若从理性的商业角度出发,这种程度的追求几乎是不可理喻的,因为它的投入产出比简直惨不忍睹,但我并不在乎。

Vary 主界面

看过我个人网站的朋友们应该都知道,设计和编程是我的两大爱好,我也一直在探索如何将它们更好地结合起来。过去几年里,我每年都会彻底重新设计和开发一次我的个人主页,以展示我在过去的一年里所学会的新东西。然而 Vary 的开发,则是将过去这些经验全部整合在一起,再闭关修炼了一年多之后,才得以协调使用六种计算机语言和十余种开源框架来实现的。

经过一个多月的测试和细节打磨,目前 Vary 的 iOS 版本已经上架 App Store,同时 Android 版本正在开发中,争取六月份上线。初版仍有些不足之处,我会在后续版本中逐渐完善,希望大家多提些建议。

最后,我开通了一个微信公众号,任何与 Vary 相关的事情都可以在这里和我交流,以及提交建议和 bug 反馈等等,有问必答。我也会将 Vary 的最新动态通过这个公众号推送给大家。如果你是一位 iOS / Android / Web 开发者,有意愿参与 Vary 的开发,也可以和我谈谈。总而言之,如果你对 Vary 感兴趣,就在微信搜索 VaryApp 或者扫描下方的二维码关注一下吧。期待你的反馈。

微信订阅号 VaryApp 的二维码

回调之 Node.js VS 串行之 Python

By: 胡中元
12 April 2018 at 18:46

Node 与 Python,都是脚本语言,有着类似的使用场景,所以在各个地方早已经互相 pk、比较过无数回了。虽然我知道编程语言之间的 VS 是一个很 low 的行为,因为他们必定是各有优势的。但今天我还是特别的想说说自己的心得体会。

Node 是我曾经特别喜欢,也是非常熟练的编程语言。Python 我还处于学习阶段,不敢说深入了解。

使用场景

如果要评论手机 App 开发,自然是有着一堆框架的 Node 胜出,而如果站在科学计算领域,那胜利者绝对是 Python。所以本文主要还是对简单的日常场景进行对比 ———— 代替 Shell 的那些操作、作为服务器中间件与数据库打交道等等。

Python 脚本

前几个周我写了数个 Python 脚本,这便是其中一个。将 NAS 中我收集的漂亮壁纸的分辨率、时间信息记录到 SQLite 数据库中,再定时从数据库中随机取出近期未使用过的壁纸,让桌面壁纸换一换。

Python 对于这种事情确实在行。另外我根据爬虫创建的翻墙规则 Shadowrocket-ADBlock-Rules 也是使用 Python 生成的,5 线程爬虫协同工作,代码清晰简洁。

Node 脚本

Node 上面提到的那些,相对而言更适合较大的项目。比如我开发的 XSYU-GMS 教务系统,就是一个前后端全栈 Javascript 项目。

对于 web 中间件,例如监听 443 端口提供数据相关的 API 调用服务。Node 原生可作为一个守护进程保持运行,灵活维护一些常量(Python 也是这样,只是感觉工作量会更大一点),作为脚本语言的高维护性体现出来,并且 Node “以事务驱动单进程” 的特性在极限情况下可以实现超高的性能。

Node 成败均在回调

继续上文。虽说 Node “以事务驱动单进程” 的特性在极限情况下可以实现超高的性能。但是对于大多数情况下,Python 也是可以撑住的。低负载的情况下,Node 引以为傲的 “事务驱动” 就变成了可怕的 “回调地狱”,在性能提升不大的情况下,严重影响了开发效率,甚至是提高了程序复杂度所导致的出错率。

我曾经在用 Node 开发 RSS 爬虫时,认真思考每一步之间的相互依赖关系,哪些可以并行,哪些必须等待。然后精确地实现,代码看起来非常复杂。实际上的效率提升其实真不是很重要,全部都用串行实现,岂不一样可以达到目的。

毕竟,要最追求效率那我应该选择 C 甚至是汇编,既然选择了 Node 这样的脚本语言,那就是为了开发方便。 实际上,计算机编程语言的发展,就是在不断地用运行效率换取开发效率。当然,不会因为开发效率彻底代替运行效率。

Promise 与 Async Function

这大概就是拯救 Node 回调地狱的存在吧,确实,程序写起来体验好太多了。但是…… 这又导致了 await 地狱…… >o<

下面是我今天的代码,功能从数据库中获取部分 Email 地址,给他们发送邮件。

(async function() {
    let maxTimeStr = new Date().toUTCString();

    ret = await sqlP(`select username from user WHERE 
        d=0 AND status=1 AND created_at line.username);


    for(addr of emailList) {
        try {
            await sendMailForTK(addr);
        }
        catch(e) {
            console.log('sendMail failed', addr, e);
            await sleep(61);
            contine;
        }

        // send Done
        sqlP('update user SET d=88 WHERE username="' + addr + '"');

        await sleep(6);
    }

    process.exit();
})();

/**
三步串行操作:
1. 从数据库取得邮箱地址
2. 发送邮件
3,将记录写回数据库
*/

代码中的 sqlP(), sleep(), sendMailForTK(),都是我自己封装的 Promise Object,虽然封装不是个麻烦事,但我有一种感觉 —— 以后几乎所有的 Node 开发,都要经历这一步了。同时必须要经过的一步那就是在调用的时候用上 await,这就是 await 地狱。

于是问题来了,既然我们 80% 需要的程序逻辑都是串行,那么把这 80% 的代码特殊处理还真是一键麻烦的没必要的事情。像 Python 那样默认为串行,在 20% 的时候再使用多线程,在我看来是一种更合适、更通用的模式。

事件驱动是 JS 的核心,所以 Node 自然以后也只能保持这种模式了。

对 Node 的总结

我认为,Node 的最佳使用场景仅限于需要其特点:”异步回调” 的时候,可以带来高性能。而其他时候,这只会带来麻烦。

当然,Node 超级给力的模块机制,以及与简单易学的 JS 搭配实现的 web 开发语言前后端同一等等,使得 JS 永远充满魅力。

Python 的缺点

就并行串行而言,Python 我更偏好,但是这门语言我还是有一些需要吐槽的地方。

1. 编程风格

对于初学的我来说,完全不能赞同 Python 那些语法就是 “优雅的编程”,而 C 风格的大括号、分号就有多难看。在 class 里面,最多只能一个空行,而不能 2 个。使用 Python 编程让我有种被限制的感觉(也许还是因为我不熟悉吧)

总之我认为 “非 C 风格编程” 是一个缺点。

2. 相比 Node 包管理机制更弱

这不是 Python 太差,而是 Node 太叼了。Node 只提供底层 API,提倡使用第三方包完成你的工作(这也是导致 JS 项目层出不穷的原因),而 Python 并没有这种提倡。

❌
❌