#6 数组按行访问和按列访问速度有这么大差异?
Clang Golang 2023-12-01看到一片技术文章《改了一行代码,数组遍历耗时从 10.3 秒降到了 0.5 秒!》,试验了一下。
coding in a complicated world
看到一片技术文章《改了一行代码,数组遍历耗时从 10.3 秒降到了 0.5 秒!》,试验了一下。
代码地址:https://github.com/antirez/smallchat
视频讲解:https://www.youtube.com/watch?v=eT02gzeLmF0
作者给做前端开发的朋友写的一个网络编程示例,只有 200 多行纯 C 代码,没有第三方依赖,使用了 select 多路复用实现命令行聊天。
可以用来复习一下 C 编程。
简化部分注释后:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/select.h>
/* ============================ 数据结构 ================================= */
#define MAX_CLIENTS 1000 // This is actually the higher file descriptor.
#define SERVER_PORT 7711
/* This structure represents a connected client. There is very little
* info about it: the socket descriptor and the nick name, if set, otherwise
* the first byte of the nickname is set to 0 if not set.
* The client can set its nickname with /nick <nickname> command. */
struct client {
int fd; // Client socket.
char *nick; // Nickname of the client.
};
/* This global structure encasulates the global state of the chat. */
struct chatState {
int serversock; // Listening server socket.
int numclients; // Number of connected clients right now.
int maxclient; // The greatest 'clients' slot populated.
struct client *clients[MAX_CLIENTS]; // Clients are set in the corresponding
// slot of their socket descriptor.
};
struct chatState *Chat; // Initialized at startup.
/* ======================== 网络方法 ========================== */
/* Create a TCP socket listening to 'port' ready to accept connections. */
int createTCPServer(int port) {
int s, yes = 1;
struct sockaddr_in sa;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); // Best effort.
memset(&sa,0,sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s,(struct sockaddr*)&sa,sizeof(sa)) == -1 ||
listen(s, 511) == -1)
{
close(s);
return -1;
}
return s;
}
/* Set the specified socket in non-blocking mode, with no delay flag. */
int socketSetNonBlockNoDelay(int fd) {
int flags, yes = 1;
/* Set the socket nonblocking.
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) return -1;
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) return -1;
/* This is best-effort. No need to check for errors. */
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));
return 0;
}
/* If the listening socket signaled there is a new connection ready to
* be accepted, we accept(2) it and return -1 on error or the new client
* socket on success. */
int acceptClient(int server_socket) {
int s;
while(1) {
struct sockaddr_in sa;
socklen_t slen = sizeof(sa);
s = accept(server_socket,(struct sockaddr*)&sa,&slen);
if (s == -1) {
if (errno == EINTR)
continue; /* Try again. */
else
return -1;
}
break;
}
return s;
}
/* We also define an allocator that always crashes on out of memory: you
* will discover that in most programs designed to run for a long time, that
* are not libraries, trying to recover from out of memory is often futile
* and at the same time makes the whole program terrible. */
void *chatMalloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) {
perror("Out of memory");
exit(1);
}
return ptr;
}
/* Also aborting realloc(). */
void *chatRealloc(void *ptr, size_t size) {
ptr = realloc(ptr,size);
if (ptr == NULL) {
perror("Out of memory");
exit(1);
}
return ptr;
}
/* ====================== 聊天服务实现 ======================== */
/* Create a new client bound to 'fd'. This is called when a new client
* connects. As a side effect updates the global Chat state. */
struct client *createClient(int fd) {
char nick[32]; // Used to create an initial nick for the user.
int nicklen = snprintf(nick,sizeof(nick),"user:%d",fd);
struct client *c = chatMalloc(sizeof(*c));
socketSetNonBlockNoDelay(fd); // Pretend this will not fail.
c->fd = fd;
c->nick = chatMalloc(nicklen+1);
memcpy(c->nick,nick,nicklen);
assert(Chat->clients[c->fd] == NULL); // This should be available.
Chat->clients[c->fd] = c;
/* We need to update the max client set if needed. */
if (c->fd > Chat->maxclient) Chat->maxclient = c->fd;
Chat->numclients++;
return c;
}
/* Free a client, associated resources, and unbind it from the global
* state in Chat. */
void freeClient(struct client *c) {
free(c->nick);
close(c->fd);
Chat->clients[c->fd] = NULL;
Chat->numclients--;
if (Chat->maxclient == c->fd) {
/* Ooops, this was the max client set. Let's find what is
* the new highest slot used. */
int j;
for (j = Chat->maxclient-1; j >= 0; j--) {
if (Chat->clients[j] != NULL) Chat->maxclient = j;
break;
}
if (j == -1) Chat->maxclient = -1; // We no longer have clients.
}
free(c);
}
/* Allocate and init the global stuff. */
void initChat(void) {
Chat = chatMalloc(sizeof(*Chat));
memset(Chat,0,sizeof(*Chat));
/* No clients at startup, of course. */
Chat->maxclient = -1;
Chat->numclients = 0;
/* Create our listening socket, bound to the given port. This
* is where our clients will connect. */
Chat->serversock = createTCPServer(SERVER_PORT);
if (Chat->serversock == -1) {
perror("Creating listening socket");
exit(1);
}
}
/* Send the specified string to all connected clients but the one
* having as socket descriptor 'excluded'. If you want to send something
* to every client just set excluded to an impossible socket: -1. */
void sendMsgToAllClientsBut(int excluded, char *s, size_t len) {
for (int j = 0; j <= Chat->maxclient; j++) {
if (Chat->clients[j] == NULL ||
Chat->clients[j]->fd == excluded) continue;
/* Important: we don't do ANY BUFFERING. We just use the kernel
* socket buffers. If the content does not fit, we don't care.
* This is needed in order to keep this program simple. */
write(Chat->clients[j]->fd,s,len);
}
}
/* The main() function implements the main chat logic:
* 1. Accept new clients connections if any.
* 2. Check if any client sent us some new message.
* 3. Send the message to all the other clients. */
int main(void) {
initChat();
while(1) {
fd_set readfds;
struct timeval tv;
int retval;
FD_ZERO(&readfds);
/* When we want to be notified by select() that there is
* activity? If the listening socket has pending clients to accept
* or if any other client wrote anything. */
FD_SET(Chat->serversock, &readfds);
for (int j = 0; j <= Chat->maxclient; j++) {
if (Chat->clients[j]) FD_SET(j, &readfds);
}
/* Set a timeout for select(), see later why this may be useful
* in the future (not now). */
tv.tv_sec = 1; // 1 sec timeout
tv.tv_usec = 0;
/* Select wants as first argument the maximum file descriptor
* in use plus one. It can be either one of our clients or the
* server socket itself. */
int maxfd = Chat->maxclient;
if (maxfd < Chat->serversock) maxfd = Chat->serversock;
retval = select(maxfd+1, &readfds, NULL, NULL, &tv);
if (retval == -1) {
perror("select() error");
exit(1);
} else if (retval) {
/* If the listening socket is "readable", it actually means
* there are new clients connections pending to accept. */
if (FD_ISSET(Chat->serversock, &readfds)) {
int fd = acceptClient(Chat->serversock);
struct client *c = createClient(fd);
/* Send a welcome message. */
char *welcome_msg =
"Welcome to Simple Chat! "
"Use /nick <nick> to set your nick.\n";
write(c->fd,welcome_msg,strlen(welcome_msg));
printf("Connected client fd=%d\n", fd);
}
/* Here for each connected client, check if there are pending
* data the client sent us. */
char readbuf[256];
for (int j = 0; j <= Chat->maxclient; j++) {
if (Chat->clients[j] == NULL) continue;
if (FD_ISSET(j, &readfds)) {
/* Here we just hope that there is a well formed
* message waiting for us. But it is entirely possible
* that we read just half a message. In a normal program
* that is not designed to be that simple, we should try
* to buffer reads until the end-of-the-line is reached. */
int nread = read(j,readbuf,sizeof(readbuf)-1);
if (nread <= 0) {
/* Error or short read means that the socket
* was closed. */
printf("Disconnected client fd=%d, nick=%s\n",
j, Chat->clients[j]->nick);
freeClient(Chat->clients[j]);
} else {
/* The client sent us a message. We need to
* relay this message to all the other clients
* in the chat. */
struct client *c = Chat->clients[j];
readbuf[nread] = 0;
/* If the user message starts with "/", we
* process it as a client command. So far
* only the /nick <newnick> command is implemented. */
if (readbuf[0] == '/') {
/* Remove any trailing newline. */
char *p;
p = strchr(readbuf,'\r'); if (p) *p = 0;
p = strchr(readbuf,'\n'); if (p) *p = 0;
/* Check for an argument of the command, after
* the space. */
char *arg = strchr(readbuf,' ');
if (arg) {
*arg = 0; /* Terminate command name. */
arg++; /* Argument is 1 byte after the space. */
}
if (!strcmp(readbuf,"/nick") && arg) {
free(c->nick);
int nicklen = strlen(arg);
c->nick = chatMalloc(nicklen+1);
memcpy(c->nick,arg,nicklen+1);
} else {
/* Unsupported command. Send an error. */
char *errmsg = "Unsupported command\n";
write(c->fd,errmsg,strlen(errmsg));
}
} else {
/* Create a message to send everybody (and show
* on the server console) in the form:
* nick> some message. */
char msg[256];
int msglen = snprintf(msg, sizeof(msg),
"%s> %s", c->nick, readbuf);
if (msglen >= (int)sizeof(msg))
msglen = sizeof(msg)-1;
printf("%s",msg);
/* Send it to all the other clients. */
sendMsgToAllClientsBut(j,msg,msglen);
}
}
}
}
} else {
/* Timeout occurred. We don't do anything right now, but in
* general this section can be used to wakeup periodically
* even if there is no clients activity. */
}
}
return 0;
}
英文原文:https://levelup.gitconnected.com/why-modern-alternative-languages-never-replace-c-c-cbf0afc5f1dc
中文翻译:https://mp.weixin.qq.com/s/tr69w96gOO7ia81ttilgVA
总结一下:
c 常量的两种实现方法:
define
宏const
关键字const
关键字const int a
/ int const a
const int *a
/ int const *a
int *const a
const int *const a
int main()
{
const int a = 1;
int *ptr = (int *)&a;
*ptr = 2;
printf("a = %d\n", a);
return 0;
}
如果用 gcc 编译会输出:a = 2
, g++ 的话,会输出 a = 1
, WHY? 为什么 C 允许修改常量,是不是一个 BUG?
参考网上诸多资料,我的理解如下:
printf
格式化limits.h
中的极限值int
, _Bool
(C99, int
别名), bool
/true
/false
(stdbool.h
)l
, ll
, u
, f
)stdint.h
)assert.h
ctype.h
errno.h
float.h
inttypes.h
iso646.h
limits.h
locale.h
math.h
signal.h
stdint.h
stdlib.h
stdio.h
string.h
time.h
wchar.h
wctype.h
// 数据类型 12
short int long double float
char struct union enum typedef
signed unsigned
// 变量 6
auto register extern const volatile
static
// 函数 2
void return
// 控制语句 11
if else for continue while do
switch case default goto break
// 其他 1
sizeof
short
, int
, long
, long long
signed
, unsigned
float
, double
char
, char *
#include <stdio.h>
int main(){
printf("size of int: %2ld\n", sizeof(int));
printf("size of short: %2ld\n", sizeof(short));
printf("size of long: %2ld\n", sizeof(long));
printf("size of long long: %2ld\n", sizeof(long long));
printf("size of float: %2ld\n", sizeof(float));
printf("size of double: %2ld\n", sizeof(double));
printf("size of long double: %2ld\n", sizeof(long double));
printf("size of char: %2ld\n", sizeof(char));
}
$ uname -a
Linux dell 5.11.0-34-generic #36-Ubuntu SMP Thu Aug 26 19:22:09 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 10.3.0-1ubuntu1) 10.3.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc /tmp/test.c -o /tmp/test && /tmp/test
size of int: 4
size of short: 2
size of long: 8
size of long long: 8
size of float: 4
size of double: 8
size of long double: 16
size of char: 1
Microsoft Visual C(简称Visual C、MSVC、VC或VC)是微软公司的C开发工具,具有集成开发环境,可提供编辑C语言,C以及C/CLI等编程语言。
MS 的 C/C++ 开发工具,继承了他们家的 Win32API,MFC,.NET 等编程框架。
最早是 MSC (Microsoft C);
后来添加 C 支持,改名叫 MS C/C;
再后来,GUI 编程兴起,改名 Visual C,简称 MSVC;
再后来,.NET 框架出来了,改名 Visual C.NET (Visual C .NET 2002);
再后来,05 年,又给改回去了 (Visual C 2005);
现在最新版本已经到了 Visual C++ 2015。
Version | Time | Notes |
---|---|---|
MSC 1.0 | 1983 | |
MSC 7.0 | 1989 | |
MS C/C++ 7.0 | 1992 | c++, mfc |
VC++ 1.0 | 1993 | 16bit 版本 |
VC++ 1.52C | 1993 | 16bit 最后版本 |
VC++ 1.0 | 1993 | 32bit 版本, 重置了版本号 |
VC++ 2.0 | Win95 发布 | |
VC++ 4.0 | 1995 | |
VC++ 5.0 | 1997 | |
VC++ 6.0 | 1998 | |
Visual C++ .NET 2002 | 2002 | 7.0 |
Visual C++ .NET 2003 | 2003 | 7.1 |
Visual C++ 2005 | 2005 | 8.0, 开始支持 64bit |
Visual C++ 2008 | 2007 | 9.0 |
Visual C++ 2010 | 2010 | 10.0 |
Visual C++ 2012 | 2012 | 11.0 |
Visual C++ 2013 | 2013 | 12.0 |
Visual C++ 2015 | 2015 | 14.0 |
Visual C++ 2017 | 2017 | 14.10 |
Visual C++ 2019 | 2019 | 14.20 |
PS: 大学学 C++ 的时候就是用的 VC6。
Managed Extensions for C, C托管扩展
微软对 C 做了一个语法拓展(属性和关键字),便于将 C 编译成 .NET 平台中间代码。
非托管代码(没有改写)可以与托管代码进行很好的互操作。
但是由于引入了大量新的代码,代码整体可读性降低了。
经过多年工程实践,微软为 Managed C 进行了大量改进。然后可能是标准化(ECMA-372)的缘故,新版本改名叫 C/CLI。
VC2005 开始使用 C/CLI 代替 Managed C。
根据文档(如何将 C++/CLI 项目移植到 .NET Core)来看,C++/CLI 项目就算移植到 .NET Core 也只能运行于 Windows。原因不明。