How does PHP communicate with the Go language?

Posted by method_man on Wed, 09 Feb 2022 04:19:29 +0100

In such a scenario, the php project needs to use a third-party function, and there happens to be a class library written in Golang. So the question comes, how to realize the communication between different languages? Let's have a look.

 

Conventional scheme

1. Write an http/TCP service with Golang, and php communicates with Golang through http/TCP

2. Golang is encapsulated as a php extension.

3. PHP calls the executable file of Golang through the system command

 

Existing problems

1. http request, network I/O will consume a lot of time

2. Need to encapsulate a lot of code

3. Every time PHP calls the Golang program, it needs an initialization, which consumes a lot of time

 

Optimization objectives

1. The Golang program is initialized only once (because initialization is time-consuming)

2. All requests do not need to go online

3. Try not to modify the code a lot

 

Solution

1. The simple Golang encapsulation compiles the third-party class library into an executable file

2. PHP communicates with Golang through a two-way pipeline

 

Advantages of using two-way pipeline communication

1: Only a small amount of encapsulation is required for the original Golang class library

2: Best performance (IPC communication is the best way to communicate between processes)

3: No network request is needed, which saves a lot of time

4: The program only needs to be initialized once and remains in memory all the time

Specific implementation steps

1: Original call demo in class library

package main
import (
 "fmt"
 "github.com/yanyiwu/gojieba"
 "strings"
)

func main() {
 x := gojieba.NewJieba()
 defer x.Free()

 s := "Xiao Ming graduated from the Institute of computing of the Chinese Academy of Sciences and later studied at Kyoto University in Japan"
 words := x.CutForSearch(s, true)
 fmt.Println(strings.Join(words, "/"))
}

Save the file as main Go, you can run

 

2: The adjusted code is:

package main
import (
 "bufio"
 "fmt"
 "github.com/yanyiwu/gojieba"
 "io"
 "os"
 "strings"
)

func main() {

 x := gojieba.NewJieba(
  "/data/tmp/jiebaDict/jieba.dict.utf8",
  "/data/tmp/jiebaDict/hmm_model.utf8",
  "/data/tmp/jiebaDict/user.dict.utf8"
 )
 defer x.Free()

 inputReader := bufio.NewReader(os.Stdin)
 for {
  s, err := inputReader.ReadString('\n')
  if err != nil && err == io.EOF {
   break
  }
  s = strings.TrimSpace(s)

  if s != "" {
   words := x.CutForSearch(s, true)
   fmt.Println(strings.Join(words, " "))
  } else {
   fmt.Println("get empty \n")
  }
 }
}

 

It only needs a few simple lines of adjustment to realize: receive the string from the standard input, and then output it after word segmentation

Test:

# go build test
# ./test
#/ / wait for user input, enter "this is a test“
#This is a test / / program

 

3: Use cat to communicate with Golang for simple test

//Prepare a title Txt, each line is a sentence of text
# cat title.txt | ./test

Normal output indicates that cat can interact with Golang normally

 

4: Communication between PHP and Golang

The cat shown above communicates with Golang using a one-way pipe. That is, data can only be transferred from cat to Golang. The data output by Golang is not returned to cat, but directly output to the screen. But the requirement in this article is: php communicates with Golang. That is, php needs to send data to Golang, and Golang must also return the execution result to php. Therefore, two-way pipeline needs to be introduced.

The use of pipeline in PHP: popen("/path/test"), I won't expand it, because this method can't solve the problem in this paper.

Two way pipe:

$descriptorspec = array(
 0 => array("pipe", "r"),
 1 => array("pipe", "w")
);
$handle = proc_open(
 '/webroot/go/src/test/test',
 $descriptorspec,
 $pipes
);
fwrite($pipes['0'], "This is a test text\n");
echo fgets($pipes[1]);

 

Explanation: using proc_open opens a process and calls the Golang program. At the same time, a bidirectional pipeline pipes array is returned. php writes data to $pipe['0 '] and reads data from $pipe['1'].

Well, maybe you've found that I'm the title file. The focus here is not just how PHP communicates with Golang. Instead, we are introducing a method to let any language communicate through two-way pipeline. (all languages implement pipeline related content)

Test:

Through the comparative test, the time occupied by each process is calculated. The title mentioned below Txt file, containing 1 million lines of text, each line of text is the product title taken from the b2b platform

1: The overall process takes time

time cat title.txt | ./test > /dev/null

Time consuming: 14.819 seconds, including:

Process cat reads text

Pipe data into Golang

Golang processes the data and returns the results to the screen

 

2: It takes time to calculate the word segmentation function. Scheme: remove the calling of word segmentation function, that is, comment out the code in the line of calling word segmentation in Golang source code

time cat title.txt | ./test > /dev/null

Time consumption: 1.817 seconds, including:

Process cat reads text

Pipe data into Golang

Golang processes the data and returns the results to the screen

Word segmentation time = (time spent in the first step) - (time spent in the above command)

Word segmentation time: 14.819 - 1.817 = 13.002 seconds

 

3: Test the time spent communicating between the cat process and the Golang process

time cat title.txt > /dev/null

Time consumption: 0.015 seconds, including:

Process cat reads text

Pipe data into Golang

go processes the data and returns the results to the screen

Pipeline communication time consuming: (time consuming in the second step) - (time consuming in the third step)

Pipeline communication time: 1.817 - 0.015 = 1.802 seconds

 

4: Time consumption of communication between PHP and Golang

Write a simple php file:

<?php
 $descriptorspec = array(
  0 => array("pipe", "r"),
  1 => array("pipe", "w")
 );

 $handle = proc_open(
  '/webroot/go/src/test/test',
  $descriptorspec,
  $pipes
 );

 $fp = fopen("title.txt", "rb");

 while (!feof($fp)) {
  fwrite($pipes['0'], trim(fgets($fp))."\n");
  echo fgets($pipes[1]);
 }

 fclose($pipes['0']);
 fclose($pipes['1']);
 proc_close($handle);

 

The process is basically the same as above. Read out the title Txt content is passed into the Golang process through a two-way pipeline, and then returned to php (one step more than the above test: the data is returned through the pipeline)

time php popen.php > /dev/null

 

Time consuming: 24.037 seconds, including:

Process PHP reads text

Pass data into the Golang pipeline

Golang processing data

Golang writes the returned results to the pipeline, and PHP receives the data through the pipeline

Return results to screen

 

Conclusion:

1: Time-consuming distribution in the whole word segmentation process

Time consuming for using cat control logic: 14.819 seconds

Using PHP to control logic takes 24.037 seconds (one more pipeline communication than cat)

One way pipeline communication time: 1.8 seconds

The word segmentation function in Golang takes 13.002 seconds

2: Performance of word segmentation function: single process, 1 million commodity Title word segmentation, taking 13 seconds

The above time only includes word segmentation time, excluding dictionary loading time. However, in this scheme, the dictionary is loaded only once, so the loading time of the dictionary can be ignored (about 1 second)

3: PHP is slower than cat (this conclusion is a little redundant, ha ha)

Slow language level: (24.037 - 1.8 - 14.819) / 14.819 = 50%

For single process comparison test, there should be no language faster than cat.

Topics: PHP Go